Dentro la dimensione Murakami

L’obiettivo di questa analisi è quella di individuare il mondo che l’autore Haruki Murakami descrive e rappresenta attraverso i suoi misteriosi e cupi mondi, in bilico tra sogno e realtà, alla scoperta dell’esistenza umana. All’inizio dell’analisi mi sono concentrata nell’individuare gli aspetti tecnici e formali della scrittura murakamiana per poi passare all’analisi semantica dei libri basandomi principalemtne su queste domande: 1) individuare i libri che hanno fortemente influenzato Murakami 2) confronto tra primo e ultim romanzo 3) individuare temi ricorrenti nei libri 4) analizzare i libri in sequenza per individuare eventuali periodi dell’autore 5) individuare cosa vanno cercando i lettori di Murakami dai suoi romanzi, analizzando quelli più popolari

library(tidyverse)
## Warning: il pacchetto 'tidyverse' è stato creato con R versione 4.4.3
## Warning: il pacchetto 'ggplot2' è stato creato con R versione 4.4.3
## Warning: il pacchetto 'stringr' è stato creato con R versione 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidytext)
## Warning: il pacchetto 'tidytext' è stato creato con R versione 4.4.3
library(tidygraph)
## 
## Caricamento pacchetto: 'tidygraph'
## 
## Il seguente oggetto è mascherato da 'package:stats':
## 
##     filter
library(widyr)
## Warning: il pacchetto 'widyr' è stato creato con R versione 4.4.3
library(igraph)
## Warning: il pacchetto 'igraph' è stato creato con R versione 4.4.3
## 
## Caricamento pacchetto: 'igraph'
## 
## Il seguente oggetto è mascherato da 'package:tidygraph':
## 
##     groups
## 
## I seguenti oggetti sono mascherati da 'package:lubridate':
## 
##     %--%, union
## 
## I seguenti oggetti sono mascherati da 'package:dplyr':
## 
##     as_data_frame, groups, union
## 
## I seguenti oggetti sono mascherati da 'package:purrr':
## 
##     compose, simplify
## 
## Il seguente oggetto è mascherato da 'package:tidyr':
## 
##     crossing
## 
## Il seguente oggetto è mascherato da 'package:tibble':
## 
##     as_data_frame
## 
## I seguenti oggetti sono mascherati da 'package:stats':
## 
##     decompose, spectrum
## 
## Il seguente oggetto è mascherato da 'package:base':
## 
##     union
library(ggraph)
library(scales)
## Warning: il pacchetto 'scales' è stato creato con R versione 4.4.3
## 
## Caricamento pacchetto: 'scales'
## 
## Il seguente oggetto è mascherato da 'package:purrr':
## 
##     discard
## 
## Il seguente oggetto è mascherato da 'package:readr':
## 
##     col_factor
library(wordcloud)
## Warning: il pacchetto 'wordcloud' è stato creato con R versione 4.4.3
## Caricamento del pacchetto richiesto: RColorBrewer
library(RColorBrewer)

# Caricamento del dataset dei libri di murakami e dei libri ispirati da muraki
dfM <- read_rds("murakami_english.rds")
dfA <- read_rds("others_books.rds")

# Definisco un ordinamento per i libri
ordine_libri <- c(
  "1-Hear the Wind Sing",
  "2-Pinball, 1973",
  "3-A Wild Sheep Chase",
  "4-Hard Boiled Wonderland and the End of the World",
  "5-Norwegian wood",
  "6-Dance Dance Dance",
  "7-South of the Border West of the Sun",
  "8-The Wind-Up Bird Chronicle",
  "9-Sputnik Sweetheart",
  "10-Kafka On The Shore",
  "11-After Dark",
  "12-1Q84",
  "13-Colorless Tsukuru Tazaki",
  "14-Killing Commendatore",
  "15-The City and Its Uncertain"
)

# Applica factor ai dataset principali
dfM <- dfM %>%
  mutate(libro = factor(libro, levels = ordine_libri))

In questa sezione ho semplicemente caricato i dataframe dei 15 libri di Murakami (1-Hear the Wind Sing, 2-Pinball, 1973 , 3-A Wild Sheep Chase, 4-Hard Boiled Wonderland and the End of the World, 5-Norwegian wood, 6-Dance Dance Dance ,7-South of the Border West of the Sun, 8-The Wind-Up Bird Chronicle, 9-Sputnik Sweetheart, 10-Kafka On The Shore ,11-After Dark, 12-1Q84, 13-Colorless Tsukuru Tazaki, 14-Killing Commendatore, 15-The City and Its Uncertain). Inoltre ho caricato anche il dataframe con le opere principali di alcuni artisti che hanno avuto grande influenza sull’autore (1984,The Great Gatsby, The Long Goodbye, Trout Fishing in America).

#TOKENIZZAZIONE E PULIZIA

# visualizzo la struttura del dataset
glimpse(dfM)
## Rows: 615
## Columns: 5
## $ text   <chr> "Hear the Wind Sing\n\n  Haruki Murakami", "KODANSHA INTERNATIO…
## $ linea  <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, …
## $ libro  <fct> "1-Hear the Wind Sing", "1-Hear the Wind Sing", "1-Hear the Win…
## $ anno   <dbl> 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 197…
## $ genere <chr> "Surrealism", "Surrealism", "Surrealism", "Surrealism", "Surrea…
# Tokenizzazione, un token (parola) per riga 
libri_tidy <- dfM %>%
  unnest_tokens(word, text)

# Rimozione delle stop words
libri_tidy <- libri_tidy %>%
  anti_join(stop_words, by = "word")


glimpse(libri_tidy)
## Rows: 651,417
## Columns: 5
## $ linea  <int> 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
## $ libro  <fct> "1-Hear the Wind Sing", "1-Hear the Wind Sing", "1-Hear the Win…
## $ anno   <dbl> 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 197…
## $ genere <chr> "Surrealism", "Surrealism", "Surrealism", "Surrealism", "Surrea…
## $ word   <chr> "hear", "wind", "sing", "haruki", "murakami", "kodansha", "inte…
# Salvataggio 
#write_rds(libri_tidy, "murakami_english_tidy.rds")

In questa prima fase mi sono concentrata sulla preparazione del testo.

Osservando il dataframe iniziale tramite glimpse(dfM), emerge che il corpus è composto da 615 righe, ciascuna delle quali rappresenta un blocco di testo (capitoli) estratto per tutti i libri analizzati. Il dataset è organizzato in cinque colonne principali:

text, che contiene il testo vero e proprio;

linea, un identificativo progressivo del blocco;

libro, il titolo dell’opera considerata;

anno, che indica l’anno di pubblicazione;

genere, che classifica il genere letterario del libro;

A partire da questa struttura, il testo è stato sottoposto a tokenizzazione che trasforma il corpus dal formato “paragrafo” a un formato one-token-per-row che porta ad alcune trasformazioni automatiche come: - le parole vengono convertite in minuscolo - la punteggiatura viene rimossa - le informazioni contestuali (libro, anno, genere) vengono replicate per ciascun token. In questo modo, ogni parola resta sempre collegata al contesto narrativo da cui proviene.

Successivamente sono andata a rimuovere le stopword e quindi le parole molto frequenti nella lingua inglese che hanno un peso informativo ridotto e rischiano di coprire termini più significativi dal punto di vista semantico.

Ho scelto di non applicare lo stemming perché non volevo rischiare di ridurre parole diverse a una stessa radice e perdere poi le varie sfumature semantiche.

1. ANALISI ESPLORATUVA

levels(libri_tidy$libro)
##  [1] "1-Hear the Wind Sing"                             
##  [2] "2-Pinball, 1973"                                  
##  [3] "3-A Wild Sheep Chase"                             
##  [4] "4-Hard Boiled Wonderland and the End of the World"
##  [5] "5-Norwegian wood"                                 
##  [6] "6-Dance Dance Dance"                              
##  [7] "7-South of the Border West of the Sun"            
##  [8] "8-The Wind-Up Bird Chronicle"                     
##  [9] "9-Sputnik Sweetheart"                             
## [10] "10-Kafka On The Shore"                            
## [11] "11-After Dark"                                    
## [12] "12-1Q84"                                          
## [13] "13-Colorless Tsukuru Tazaki"                      
## [14] "14-Killing Commendatore"                          
## [15] "15-The City and Its Uncertain"
# 1: calcolo la lunghezza media delle parole

# Lunghezza complessiva
libri_tidy %>%
  mutate(word_length = nchar(word)) %>%
  summarise(media_lunghezza = mean(word_length))
## # A tibble: 1 × 1
##   media_lunghezza
##             <dbl>
## 1            6.12
# Lunghezza per ciascun libro 
lunghezza_per_libro <- libri_tidy %>%
  mutate(word_length = nchar(word)) %>%
  group_by(libro) %>%
  summarise(media_lunghezza = mean(word_length)) %>%
  arrange(libro)

print(lunghezza_per_libro, n = 15)
## # A tibble: 15 × 2
##    libro                                             media_lunghezza
##    <fct>                                                       <dbl>
##  1 1-Hear the Wind Sing                                         5.86
##  2 2-Pinball, 1973                                              5.99
##  3 3-A Wild Sheep Chase                                         6.19
##  4 4-Hard Boiled Wonderland and the End of the World            6.17
##  5 5-Norwegian wood                                             5.89
##  6 6-Dance Dance Dance                                          6.08
##  7 7-South of the Border West of the Sun                        6.10
##  8 8-The Wind-Up Bird Chronicle                                 6.12
##  9 9-Sputnik Sweetheart                                         6.01
## 10 10-Kafka On The Shore                                        5.95
## 11 11-After Dark                                                5.91
## 12 12-1Q84                                                      6.24
## 13 13-Colorless Tsukuru Tazaki                                  6.19
## 14 14-Killing Commendatore                                      6.22
## 15 15-The City and Its Uncertain                                6.06
# Distribuzione complessiva
libri_tidy %>%
  mutate(word_length = nchar(word)) %>%
  ggplot(aes(x = word_length)) +
  geom_histogram(binwidth = 1, fill = "steelblue", color = "black") +
  labs(title = "Word length distribution — Murakami corpus",
       x = "Word length",
       y = "Frequency") +
  theme_minimal()

# Distribuzione per libro
libri_tidy %>%
  mutate(word_length = nchar(word)) %>%
  ggplot(aes(x = word_length, fill = libro)) +
  geom_histogram(binwidth = 1, color = "black") +
  facet_wrap(~libro, scales = "free_y") +
  theme_minimal() +
  theme(legend.position = "none") +
  labs(title = "Word length distribution per book",
       x = "Word length",
       y = "Frequency")

#2: calcolo la frequenza delle parole

# Frequenze assolute globali
word_frequencies <- libri_tidy %>%
  count(word, sort = TRUE)

# Visualizzazione top 20 parole più frequenti
word_frequencies %>%
  top_n(20, n) %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(title = "Most frequent words — Murakami corpus",
       x = NULL,
       y = "Word count") +
  theme_minimal()

# Word cloud complessivo
set.seed(42)  
word_frequencies %>%
  with(wordcloud(word, n, 
                 max.words = 100,
                 random.order = FALSE,
                 colors = brewer.pal(8, "Dark2")))

# Frequenze assolute per libro
word_freq_per_book <- libri_tidy %>%
  count(libro, word, sort = TRUE)

# Frequenze relative per libro
word_freq_per_book_rel <- word_freq_per_book %>%
  group_by(libro) %>%
  mutate(proportion = n / sum(n)) %>%
  ungroup()

# Visualizzazione top 10 parole per libro
word_freq_per_book_rel %>%
  group_by(libro) %>%
  slice_max(proportion, n = 10) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, proportion, libro)) %>%
  ggplot(aes(word, proportion, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Most frequent words per book (relative frequency)",
       x = NULL,
       y = "Relative frequency") +
  theme_minimal()

# Palette ottimale per scegliere il colore più corretto
palette_wordcloud <- brewer.pal(8, "Set1")

unique_books <- levels(libri_tidy$libro) #individuo i libri

# Loop attraverso i libri nell'ordine corretto
for (book_title in unique_books) {
  cat("\n=== Word Cloud for:", book_title, "===\n")
  
  book_data <- word_freq_per_book %>%
    filter(libro == book_title) %>%
    slice_max(n, n = 100)
  
  # Verifica che ci siano dati
  if (nrow(book_data) > 0) {
    set.seed(42)
    wordcloud(words = book_data$word,
              freq = book_data$n,
              max.words = 100,
              random.order = FALSE,
              rot.per = 0.2,
              colors = palette_wordcloud,
              scale = c(4, 0.5))
  }
}
## 
## === Word Cloud for: 1-Hear the Wind Sing ===

## 
## === Word Cloud for: 2-Pinball, 1973 ===

## 
## === Word Cloud for: 3-A Wild Sheep Chase ===

## 
## === Word Cloud for: 4-Hard Boiled Wonderland and the End of the World ===

## 
## === Word Cloud for: 5-Norwegian wood ===

## 
## === Word Cloud for: 6-Dance Dance Dance ===

## 
## === Word Cloud for: 7-South of the Border West of the Sun ===

## 
## === Word Cloud for: 8-The Wind-Up Bird Chronicle ===

## 
## === Word Cloud for: 9-Sputnik Sweetheart ===

## 
## === Word Cloud for: 10-Kafka On The Shore ===

## 
## === Word Cloud for: 11-After Dark ===

## 
## === Word Cloud for: 12-1Q84 ===

## 
## === Word Cloud for: 13-Colorless Tsukuru Tazaki ===

## 
## === Word Cloud for: 14-Killing Commendatore ===

## 
## === Word Cloud for: 15-The City and Its Uncertain ===

#3: calcolo la legge di zipf

# Calcolo rank e term frequency per il dataset complessivo
zipf_data <- word_frequencies %>%
  mutate(rank = row_number(),
         `term frequency` = n / sum(n))

# Plot log-log 
zipf_data %>%
  ggplot(aes(rank, `term frequency`)) +
  geom_line(linewidth = 1.1, alpha = 0.8, color = "red") + 
  scale_x_log10() +
  scale_y_log10() +
  labs(title = "Zipf's law — Murakami corpus",
       x = "Rank",
       y = "Term frequency") +
  theme_minimal()

# Fitting della power law
rank_subset <- zipf_data %>%
  filter(rank > 10, rank < 500)

mod <- lm(log10(`term frequency`) ~ log10(rank), data = rank_subset)

# Stampa coefficienti
cat("\n=== Zipf's law model ===\n")
## 
## === Zipf's law model ===
cat("Intercept:", mod$coefficients[1], "\n")
## Intercept: -1.919312
cat("Slope:", mod$coefficients[2], "\n")
## Slope: -0.5444061
# Plot con linea di fitting
zipf_data %>%
  ggplot(aes(rank, `term frequency`)) +
  geom_abline(intercept = mod$coefficients[1],
              slope = mod$coefficients[2],
              color = "gray50",
              linetype = 2,
              linewidth = 1) +
  geom_line(linewidth = 1.1, alpha = 0.8, color = "red") +
  scale_x_log10() +
  scale_y_log10() +
  labs(title = "Zipf's law with power law fit — Murakami corpus",
       subtitle = paste0("Slope = ", round(mod$coefficients[2], 3)),
       x = "Rank",
       y = "Term frequency") +
  theme_minimal()

# calcolo la legge di zipf per ogni libro
zipf_per_book <- word_freq_per_book %>%
  group_by(libro) %>%
  mutate(rank = row_number(),
         `term frequency` = n / sum(n))

zipf_per_book %>%
  ggplot(aes(rank, `term frequency`, color = libro)) +
  geom_line(linewidth = 1.1, alpha = 0.8, show.legend = FALSE) +
  scale_x_log10() +
  scale_y_log10() +
  facet_wrap(~libro, scales = "free") +
  labs(title = "Zipf's law per book",
       x = "Rank",
       y = "Term frequency") +
  theme_minimal()

#4: individuo le parole distintive per ogni libro con tf-idf

book_words <- libri_tidy %>%
  count(libro, word, sort = TRUE)

# Calcolo tf-idf
book_words <- book_words %>%
  bind_tf_idf(word, libro, n)

# Verifica: parole con tf-idf più alto
cat("\n=== Top tf-idf words across all books ===\n")
## 
## === Top tf-idf words across all books ===
book_words %>%
  arrange(desc(tf_idf)) %>%
  print(n = 20)
## # A tibble: 117,855 × 6
##    libro                                 word          n      tf   idf tf_idf
##    <fct>                                 <chr>     <int>   <dbl> <dbl>  <dbl>
##  1 13-Colorless Tsukuru Tazaki           tsukuru     892 0.0315   2.01 0.0635
##  2 12-1Q84                               tengo      2860 0.0207   2.71 0.0561
##  3 12-1Q84                               aomame     2164 0.0157   2.71 0.0424
##  4 11-After Dark                         mari        335 0.0207   2.01 0.0417
##  5 14-Killing Commendatore               menshiki    930 0.0120   2.71 0.0326
##  6 9-Sputnik Sweetheart                  sumire      433 0.0201   1.32 0.0266
##  7 10-Kafka On The Shore                 hoshino     528 0.00924  2.71 0.0250
##  8 11-After Dark                         kaoru       149 0.00921  2.71 0.0249
##  9 5-Norwegian wood                      midori      312 0.00866  2.71 0.0235
## 10 6-Dance Dance Dance                   yuki        340 0.00865  2.71 0.0234
## 11 9-Sputnik Sweetheart                  miu         379 0.0176   1.32 0.0233
## 12 5-Norwegian wood                      reiko       295 0.00819  2.71 0.0222
## 13 14-Killing Commendatore               mariye      575 0.00744  2.71 0.0202
## 14 11-After Dark                         takahashi   154 0.00952  2.01 0.0192
## 15 13-Colorless Tsukuru Tazaki           haida       187 0.00661  2.71 0.0179
## 16 10-Kafka On The Shore                 oshima      371 0.00649  2.71 0.0176
## 17 6-Dance Dance Dance                   gotanda     233 0.00593  2.71 0.0160
## 18 7-South of the Border West of the Sun shimamoto   172 0.00968  1.61 0.0156
## 19 13-Colorless Tsukuru Tazaki           sara        218 0.00771  2.01 0.0155
## 20 12-1Q84                               fuka        785 0.00568  2.71 0.0154
## # ℹ 117,835 more rows
p_tfidf_parte1 <- book_words %>%
  filter(libro %in% levels(libro)[1:8]) %>%  
  group_by(libro) %>%
  top_n(15, tf_idf) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, tf_idf, libro)) %>%
  ggplot(aes(word, tf_idf, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, ncol = 3, scales = "free") + 
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Distinctive words per book (tf-idf)",
       x = NULL,
       y = "tf-idf") +
  theme_minimal(base_size = 10) + 
  theme(
    strip.text = element_text(size = 9, face = "bold"), 
    axis.text.y = element_text(size = 7),  
    axis.text.x = element_text(size = 7)
  )

p_tfidf_parte2 <- book_words %>%
  filter(libro %in% levels(libro)[9:15]) %>%  
  group_by(libro) %>%
  top_n(15, tf_idf) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, tf_idf, libro)) %>%
  ggplot(aes(word, tf_idf, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, ncol = 3, scales = "free") +  
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Distinctive words per book (tf-idf)",
       x = NULL,
       y = "tf-idf") +
  theme_minimal(base_size = 10) +  
  theme(
    strip.text = element_text(size = 9, face = "bold"),  
    axis.text.y = element_text(size = 7),  
    axis.text.x = element_text(size = 7)
  )

 print(p_tfidf_parte1)

 print(p_tfidf_parte2)

L’analisi della lunghezza media delle parole evidenzia che il vocabolario di Murakami, anche dopo la rimozione delle stopword, risulta mediamente più complesso rispetto all’inglese standard (4.7-5.5 caratteri). Questo avviene perchè Murakami predilige una scrittura densa, precisa, in cui ogni parola tende a portare con sé un particolare peso semantico.

Osservando l’evoluzione nel tempo, emerge una progressiva maturazione stilistica. I romanzi dell’esordio, come Hear the Wind Sing (5.86 caratteri), mostrano un linguaggio più essenziale e diretto, coerente con l’estetica minimalista degli anni Settanta e con l’influenza del romanzo americano mentre le opere più tarde come 1Q84 (6.24 caratteri) o Killing Commendatore (6.22) mantengono invece una complessità lessicale più elevata e stabile.

Per quanto riguarda le frequenze delle parole, emerge in maniera evidente l’ossessione che Murakami ha per il tempo. La parola time con 6,648 occorrenze domina l’intero corpus e trova eco in termini come day e night che confermano l’importanza che la parola assume durante lo sviluppo della narrativa. Il tempo non viene trattato come un semplice riferimento cronologico, ma come una riflessione esistenziale sulla natura sfuggente del tempo, sulla memoria, sulla perdita e sul rimpianto. Dai mondi paralleli di 1Q84 alla nostalgia dolorosa di Norwegian Wood, il tempo è sempre qualcosa che sfugge, si deforma o ritorna sotto forma di ricordo.

Accanto al tempo, un altro nucleo centrale riguarda la percezione sensoriale e in particolare quella visiva. Parole come eyes (2,275) e looked (2,175) compaiono con frequenza e rimandano a protagonisti contemplativi, osservatori passivi della realtà più che attivi. Nei romanzi di Murakami, lo sguardo diventa spesso un canale di accesso all’interiorità del personaggio, guardare significa cercare un senso alle dinamiche narrate, ma anche un atto di distanza dal mondo.

La triade “people” (2,814), “world” (2,240), “life” (1,900+) evidenzia la vocazione filosofica di Murakami che si concentra su tre temi principali:

Il rapporto tra l’individuo e la collettività (“people”) La natura della realtà e delle realtà parallele (“world”) Il senso dell’esistenza (“life”)

Questa dimensione universale coesiste con quella intimista, creando il caratteristico equilibrio murakamiano tra quotidiano e metafisico.

Durante l’analisi è evidente il peso rilevante di 1Q84 all’interno del corpus. Essendo uno dei romanzi più lunghi e complessi dell’artista (oltre 1,300 pagine nelle edizioni inglesi), i nomi Tengo e Aomame emergono in modo dominante, riflettendo la struttura duale nel quale i due protagonisti coesistono, costruita su due linee narrative parallele che si rispecchiano e si rincorrono. La ripetizione insistita dei nomi propri, più che dei pronomi, è coerente con una tecnica narrativa che mantiene una certa distanza emotiva, pur insistendo sull’identità individuale dei personaggi.

I word cloud permettono di cogliere visivamente ciò che le frequenze suggeriscono, ogni romanzo possiede una propria identità tematica ben riconoscibile. I primi lavori ruotano attorno a oggetti quotidiani e simboli ricorrenti (bar, sigarette, flipper),

In After Dark domina la parola time affiancata dal nome Mari, un dato che riflette perfettamente la struttura del romanzo concepito come una lunga notte urbana scandita quasi in tempo reale.

Norwegian Wood presenta invece una configurazione fortemente relazionale. I nomi Naoko e Midori emergono con peso quasi identico, confermando la centralità del triangolo emotivo che struttura il romanzo. Il lessico è intimo e corporeo, legato alla vita quotidiana e alle relazioni. Nel word cloude si nota l’assenza relativa della parola love, mai esplicitata ma costantemente vissuta attraverso memoria, dolore e perdita.

In Dance Dance Dance, la parola dominante è hotel, a indicare come lo spazio (in particolare il Dolphin Hotel) assuma un ruolo centrale e simbolico. Il romanzo è attraversato da un lessico urbano e percettivo che richiama l’estetica hard-boiled e mette in scena personaggi diversi per età e visione del mondo, accomunati da una profonda alienazione. L’assenza della parola dance sottolinea come il titolo alluda non a un’azione concreta, ma a una condizione esistenziale, il continuare a muoversi senza comprenderne fino in fondo il senso.

Nel loro insieme, queste tre word cloud funzionano come veri e propri ritratti narrativi. After Dark è dominato dal tempo e dall’osservazione, Norwegian Wood dalla memoria e dalle relazioni, Dance Dance Dance dallo spazio urbano e dalla perdita di identità. Le visualizzazioni rendono così immediatamente visibile l’anima di ciascun romanzo.

Dal punto di vista linguistico, l’applicazione della legge di Zipf conferma una notevole coerenza stilistica, nonostante l’arco temporale di oltre quarant’anni e la varietà di temi affrontati, Murakami mantiene una distribuzione lessicale sorprendentemente uniforme. Questo suggerisce che ciò che cambia radicalmente da un romanzo all’altro non è tanto il linguaggio di base, quanto l’universo narrativo che quel linguaggio abita. Nel caso di Murakami, lo slope meno ripido (-0.544) indica che la distribuzione delle frequenze decade più lentamente del previsto e questo perchè lo stile di murakami è molto minimalista.

Infine, l’analisi TF-IDF rafforza questa intuizione, le parole che distinguono maggiormente i romanzi tra loro sono quasi sempre i nomi dei personaggi. Murakami utilizza lo stesso vocabolario fondamentale per raccontare storie molto diverse, sono i personaggi, con i loro nomi e le loro ossessioni, a definire l’identità di ciascun libro.

Murakami costruisce un universo linguistico riconoscibile, all’interno del quale ogni romanzo rappresenta una nuova declinazione di temi, simboli e personaggi. È proprio questo che rendere le sue opere così immediatamente identificabili e rappresentative.

2. N-GRAMMI E CO-OCCORRENZE

# 1: BIGRAMMI

# Tokenizzazione in bigrammi 
bigrams <- dfM %>%
  unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
  filter(!is.na(bigram))

bigrams_separated <- bigrams %>%
  separate(bigram, c("word1", "word2"), sep = " ")

bigrams_filtered <- bigrams_separated %>%
  filter(!word1 %in% stop_words$word,
         !word2 %in% stop_words$word)

bigram_counts <- bigrams_filtered %>%
  count(word1, word2, sort = TRUE)

cat("\n=== Top 20 bigrams (corpus) ===\n")
## 
## === Top 20 bigrams (corpus) ===
print(bigram_counts, n = 20)
## # A tibble: 120,768 × 3
##    word1      word2            n
##    <chr>      <chr>        <int>
##  1 fuka       eri            712
##  2 air        chrysalis      317
##  3 miss       saeki          193
##  4 noboru     wataya         182
##  5 creta      kano           165
##  6 tomohiko   amada          149
##  7 deep       breath         141
##  8 malta      kano           119
##  9 left       hand           118
## 10 elementary school         114
## 11 middle     aged           113
## 12 dolphin    hotel          112
## 13 front      door           110
## 14 ten        minutes        109
## 15 time       ago            105
## 16 real       world          103
## 17 yellow     submarine      102
## 18 killing    commendatore    96
## 19 coffee     shop            86
## 20 shoulder   bag             86
## # ℹ 120,748 more rows
# Ricostruisci bigrammi come stringa 
bigrams_united <- bigrams_filtered %>%
  unite(bigram, word1, word2, sep = " ")

bigram_counts_per_book <- bigrams_united %>%
  count(libro, bigram, sort = TRUE)

#visualizzazzione dei bigrammi
p_bigrams_parte1 <- bigram_counts_per_book %>%
  filter(libro %in% levels(libro)[1:8]) %>%
  group_by(libro) %>%
  top_n(10, n) %>%
  ungroup() %>%
  mutate(bigram = reorder_within(bigram, n, libro)) %>%
  ggplot(aes(bigram, n, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, scales = "free", ncol = 3) +  
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Most frequent bigrams per book",
       x = NULL, 
       y = "Count") +
  theme_minimal(base_size = 10) +
  theme(
    strip.text = element_text(size = 9, face = "bold"), 
    axis.text.y = element_text(size = 7),  
    axis.text.x = element_text(size = 7)
  )

p_bigrams_parte2 <- bigram_counts_per_book %>%
  filter(libro %in% levels(libro)[9:15]) %>%
  group_by(libro) %>%
  top_n(10, n) %>%
  ungroup() %>%
  mutate(bigram = reorder_within(bigram, n, libro)) %>%
  ggplot(aes(bigram, n, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, scales = "free", ncol = 3) +  
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Most frequent bigrams per book",
       x = NULL, 
       y = "Count") +
  theme_minimal(base_size = 10) +
  theme(
    strip.text = element_text(size = 9, face = "bold"), 
    axis.text.y = element_text(size = 7),  
    axis.text.x = element_text(size = 7)
  )

print(p_bigrams_parte1)

print(p_bigrams_parte2)

# 2. TRIGRAMMI

trigrams <- dfM %>%
  unnest_tokens(trigram, text, token = "ngrams", n = 3) %>%
  filter(!is.na(trigram)) %>%
  separate(trigram, c("word1", "word2", "word3"), sep = " ") %>%
  filter(!word1 %in% stop_words$word,
         !word2 %in% stop_words$word,
         !word3 %in% stop_words$word) %>%
  unite(trigram, word1, word2, word3, sep = " ")

trigram_counts_per_book <- trigrams %>%
  count(libro, trigram, sort = TRUE)

cat("\n=== Top 20 trigrams (corpus) ===\n")
## 
## === Top 20 trigrams (corpus) ===
trigrams %>%
  count(trigram, sort = TRUE) %>%
  print(n = 20)
## # A tibble: 45,778 × 2
##    trigram                     n
##    <chr>                   <int>
##  1 yellow submarine boy       64
##  2 white subaru forester      61
##  3 boy named crow             40
##  4 fuka eri nodded            32
##  5 literature 978 0           27
##  6 nhk fee collector          27
##  7 ten thousand yen           27
##  8 middle aged woman          22
##  9 fiction literature 978     20
## 10 fuka eri shook             19
## 11 wild sheep chase           18
## 12 yellow submarine parka     18
## 13 978 0 375                  16
## 14 mercury vapor lamp         16
## 15 978 0 679                  15
## 16 head librarian's office    15
## 17 le mal du                  15
## 18 red vinyl hat              15
## 19 rewriting air chrysalis    15
## 20 hundred thousand yen       14
## # ℹ 45,758 more rows
#visualizzazione istogramma
p_trigrams_parte1 <- trigram_counts_per_book %>%
  filter(libro %in% levels(libro)[1:8]) %>%
  group_by(libro) %>%
  top_n(5, n) %>%
  ungroup() %>%
  mutate(trigram = reorder_within(trigram, n, libro)) %>%
  ggplot(aes(trigram, n, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, scales = "free", ncol = 3) +  
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Most frequent trigrams per book",
       x = NULL, 
       y = "Count") +
  theme_minimal(base_size = 10) +
  theme(
    strip.text = element_text(size = 9, face = "bold"), 
    axis.text.y = element_text(size = 7),  
    axis.text.x = element_text(size = 7)
  )

p_trigrams_parte2 <- trigram_counts_per_book %>%
  filter(libro %in% levels(libro)[9:15]) %>%
  group_by(libro) %>%
  top_n(5, n) %>%
  ungroup() %>%
  mutate(trigram = reorder_within(trigram, n, libro)) %>%
  ggplot(aes(trigram, n, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, scales = "free", ncol = 3) +  
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Most frequent trigrams per book",
       x = NULL, 
       y = "Count") +
  theme_minimal(base_size = 10) +
  theme(
    strip.text = element_text(size = 9, face = "bold"), 
    axis.text.y = element_text(size = 7),  
    axis.text.x = element_text(size = 7)
  )

print(p_trigrams_parte1)

print(p_trigrams_parte2)

# 3. TF-IDF DEI BIGRAMMI
bigram_tf_idf <- bigrams_united %>%
  count(libro, bigram) %>%
  bind_tf_idf(bigram, libro, n) %>%
  arrange(desc(tf_idf))

cat("\n=== Top bigrams by tf-idf ===\n")
## 
## === Top bigrams by tf-idf ===
print(bigram_tf_idf, n = 20)
## # A tibble: 141,070 × 6
##    libro                         bigram                   n      tf   idf tf_idf
##    <fct>                         <chr>                <int>   <dbl> <dbl>  <dbl>
##  1 12-1Q84                       fuka eri               712 0.0179   2.71 0.0485
##  2 10-Kafka On The Shore         miss saeki             193 0.0124   2.71 0.0337
##  3 8-The Wind-Up Bird Chronicle  noboru wataya          182 0.00939  2.71 0.0254
##  4 8-The Wind-Up Bird Chronicle  creta kano             165 0.00852  2.71 0.0231
##  5 15-The City and Its Uncertain yellow submarine       102 0.00807  2.71 0.0219
##  6 12-1Q84                       air chrysalis          317 0.00798  2.71 0.0216
##  7 14-Killing Commendatore       tomohiko amada         149 0.00780  2.71 0.0211
##  8 11-After Dark                 eri asai                39 0.00777  2.71 0.0210
##  9 3-A Wild Sheep Chase          sheep professor         70 0.00841  2.01 0.0169
## 10 8-The Wind-Up Bird Chronicle  malta kano             119 0.00614  2.71 0.0166
## 11 10-Kafka On The Shore         johnnie walker          85 0.00548  2.71 0.0148
## 12 6-Dance Dance Dance           dolphin hotel           80 0.00732  2.01 0.0148
## 13 15-The City and Its Uncertain submarine boy           64 0.00507  2.71 0.0137
## 14 9-Sputnik Sweetheart          ferris wheel            29 0.00479  2.71 0.0130
## 15 2-Pinball, 1973               pinball machines        18 0.00460  2.71 0.0125
## 16 10-Kafka On The Shore         colonel sanders         66 0.00425  2.71 0.0115
## 17 1-Hear the Wind Sing          california girls         9 0.00410  2.71 0.0111
## 18 14-Killing Commendatore       tomohiko amada's        75 0.00393  2.71 0.0106
## 19 2-Pinball, 1973               switch panel            20 0.00512  2.01 0.0103
## 20 14-Killing Commendatore       killing commendatore    95 0.00498  2.01 0.0100
## # ℹ 141,050 more rows
# Visualizzazione tf-idf bigrammi
p_bigram_tfidf_1 <- bigram_tf_idf %>%
  filter(libro %in% levels(libro)[1:8]) %>%
  group_by(libro) %>%
  top_n(12, tf_idf) %>%
  ungroup() %>%
  mutate(bigram = reorder_within(bigram, tf_idf, libro)) %>%
  ggplot(aes(bigram, tf_idf, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, ncol = 3, scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Distinctive bigrams per book (tf-idf)",
       x = NULL, y = "tf-idf") +
  theme_minimal(base_size = 9) +
  theme(
    axis.text.y = element_text(size = 6),
    strip.text = element_text(size = 8, face = "bold")
  )

p_bigram_tfidf_2 <- bigram_tf_idf %>%
  filter(libro %in% levels(libro)[9:15]) %>%
  group_by(libro) %>%
  top_n(12, tf_idf) %>%
  ungroup() %>%
  mutate(bigram = reorder_within(bigram, tf_idf, libro)) %>%
  ggplot(aes(bigram, tf_idf, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, ncol = 3, scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Distinctive bigrams per book (tf-idf)",
       x = NULL, y = "tf-idf") +
  theme_minimal(base_size = 9) +
  theme(
    axis.text.y = element_text(size = 6),
    strip.text = element_text(size = 8, face = "bold")
  )

print(p_bigram_tfidf_1)

print(p_bigram_tfidf_2)

# 4. GRAFI DEI BIGRAMMI 
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

# Grafo complessivo
cat("\n=== Creating bigram network graph ===\n")
## 
## === Creating bigram network graph ===
bigram_graph <- bigram_counts %>%
  filter(n > 50) %>% 
  as_tbl_graph()

p_graph_all <- ggraph(bigram_graph, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), 
                 show.legend = FALSE,
                 arrow = a, 
                 end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "steelblue", size = 3) +
  geom_node_text(aes(label = name), 
                 vjust = 1.5, hjust = 1, 
                 size = 3, check_overlap = TRUE) +
  theme_void() +
  labs(title = "Bigram network — Murakami corpus (n > 50)")

print(p_graph_all)

# Grafi PER LIBRO

libri_esempio <- c("5-Norwegian wood",
                   "6-Dance Dance Dance",
                   "11-After Dark", 
                   "12-1Q84")

for (book_title in libri_esempio) {
  
  cat("\n--- Bigram graph:", book_title, "---\n")
  
  
  book_bigrams <- bigrams_filtered %>%
    filter(libro == book_title) %>%
    count(word1, word2, sort = TRUE)
  
  book_graph <- book_bigrams %>%
    filter(n > 10) %>% 
    as_tbl_graph()
  
  p <- ggraph(book_graph, layout = "fr") +
    geom_edge_link(aes(edge_alpha = n), 
                   show.legend = FALSE,
                   arrow = a, 
                   end_cap = circle(.07, 'inches')) +
    geom_node_point(color = "lightblue", size = 4) +
    geom_node_text(aes(label = name), 
                   vjust = 1.5, hjust = 1, 
                   size = 3.5, check_overlap = TRUE) +
    theme_void() +
    labs(title = paste("Bigram network —", book_title))
  
  print(p)
}
## 
## --- Bigram graph: 5-Norwegian wood ---

## 
## --- Bigram graph: 6-Dance Dance Dance ---

## 
## --- Bigram graph: 11-After Dark ---

## 
## --- Bigram graph: 12-1Q84 ---

# 5. CO-OCCORRENZE 
# Co-occorrenze complessiva
section_words <- libri_tidy %>%
  mutate(section = row_number() %/% 10) %>%
  filter(section > 0)

word_pairs_raw <- section_words %>%
  pairwise_count(word, section, sort = TRUE, upper = FALSE)

cat("\n=== Top 20 word co-occurrences (deduplicated) ===\n")
## 
## === Top 20 word co-occurrences (deduplicated) ===
print(word_pairs_raw, n = 20)
## # A tibble: 1,861,590 × 3
##    item1  item2         n
##    <chr>  <chr>     <dbl>
##  1 eri    fuka        606
##  2 head   shook       495
##  3 eyes   closed      339
##  4 eri    tengo       308
##  5 tengo  fuka        304
##  6 air    chrysalis   280
##  7 time   tengo       235
##  8 wind   bird        233
##  9 time   people      217
## 10 time   eyes        214
## 11 aomame tengo       213
## 12 time   left        201
## 13 time   day         199
## 14 time   head        193
## 15 time   world       193
## 16 time   life        185
## 17 time   looked      184
## 18 eyes   looked      180
## 19 miss   saeki       176
## 20 time   mind        172
## # ℹ 1,861,570 more rows
#grafo complessivo
p_cooc_all <- word_pairs_raw %>%
  filter(n > 50) %>%
  as_tbl_graph(directed = FALSE) %>%
  ggraph(layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE) +
  geom_node_point(color = "steelblue", size = 3) +
  geom_node_text(aes(label = name), 
                 repel = TRUE, size = 3, 
                 max.overlaps = 15) +
  theme_void() +
  labs(title = "Word co-occurrence network — Murakami corpus (n > 50)")

print(p_cooc_all)
## Warning: ggrepel: 84 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps

# Co-occorrenze PER LIBRO 
for (book_title in libri_esempio) {
  
  cat("\n--- Co-occurrence:", book_title, "---\n")
  
  book_sections <- libri_tidy %>%
    filter(libro == book_title) %>%
    mutate(section = row_number() %/% 10) %>%
    filter(section > 0)
  
  book_pairs_raw <- book_sections %>%
    pairwise_count(word, section, sort = TRUE)
  
  book_pairs <- book_pairs_raw %>%
    mutate(
      word_a = if_else(item1 < item2, item1, item2),
      word_b = if_else(item1 < item2, item2, item1)
    ) %>%
    group_by(word_a, word_b) %>%
    summarise(n = sum(n), .groups = "drop") %>%
    rename(item1 = word_a, item2 = word_b) %>%
    arrange(desc(n))
 
  book_pairs_top <- book_pairs %>%
    slice_max(n, n = 40)
  

  p <- book_pairs_top %>%
    filter(n > 15) %>%  # 
    as_tbl_graph(directed = FALSE) %>%
    ggraph(layout = "fr") +
    geom_edge_link(aes(edge_alpha = n, edge_width = n), 
                   show.legend = FALSE,
                   color = "gray70") + 
    geom_node_point(color = "steelblue", size = 5) + 
    geom_node_text(aes(label = name), 
                   repel = TRUE, 
                   size = 4,  
                   max.overlaps = 20, 
                   force = 2, 
                   box.padding = 0.5) + 
    theme_void() +
    labs(title = paste("Co-occurrence network —", book_title),
         subtitle = "(Top 40 pairs, n > 15)")
  
  print(p)
}
## 
## --- Co-occurrence: 5-Norwegian wood ---
## Warning: The `trans` argument of `continuous_scale()` is deprecated as of ggplot2 3.5.0.
## ℹ Please use the `transform` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

## 
## --- Co-occurrence: 6-Dance Dance Dance ---

## 
## --- Co-occurrence: 11-After Dark ---

## 
## --- Co-occurrence: 12-1Q84 ---

# 6. CORRELAZIONI (PHI COEFFICIENT)

for (book_title in libri_esempio) {
  
  cat("\n--- Correlations:", book_title, "---\n")
  
  book_sections <- libri_tidy %>%
    filter(libro == book_title) %>%
    mutate(section = row_number() %/% 10) %>%
    filter(section > 0)
  
  # Filtra parole frequenti (almeno 20 occorrenze)
  word_cors <- book_sections %>%
    group_by(word) %>%
    filter(n() >= 20) %>%
    ungroup() %>%
    pairwise_cor(word, section, sort = TRUE)
  
  # Grafo correlazioni
  p <- word_cors %>%
    filter(correlation > .20) %>%  
    as_tbl_graph(directed = FALSE) %>%
    ggraph(layout = "fr") +
    geom_edge_link(aes(edge_alpha = correlation), 
                   show.legend = FALSE) +
    geom_node_point(color = "lightblue", size = 4) +
    geom_node_text(aes(label = name), 
                   repel = TRUE, size = 3.5,
                   max.overlaps = 15) +
    theme_void() +
    labs(title = paste("Word correlation network —", book_title))
  
  print(p)
}
## 
## --- Correlations: 5-Norwegian wood ---

## 
## --- Correlations: 6-Dance Dance Dance ---

## 
## --- Correlations: 11-After Dark ---

## 
## --- Correlations: 12-1Q84 ---

L’analisi di bigrammi, trigrammi e co-occorrenze mostra con chiarezza che la narrativa di Murakami non è costruita solo a livello tematico, ma anche attraverso formule linguistiche ricorrenti. I bigrammi sono coppie di parole consecutive che, analizzate insieme, rivelano pattern narrativi e stilistici e i bigrammi più frequenti nel corpus confermano la centralità dei nomi composti dei personaggi – Fuka Eri (712), Miss Saeki, Noboru Wataya, Dolphin Hotel – che funzionano come veri e propri nuclei semantici. I nomi diventano presenze rituali e la loro ripetizione costruisce familiarità, ossessione e identità.

Accanto ai nomi emergono espressioni corporee e temporali estremamente regolari: shook head, closed eyes, deep breath (141), ten minutes (109), time ago (105). Il corpo, in Murakami, è il primo strumento narrativo: i personaggi non spiegano ciò che provano, lo manifestano attraverso gesti minimi e ripetitivi. Allo stesso tempo, il tempo è quasi sempre misurato, cronometrico, mai astratto. Questo ancoraggio numerico e fisico rende credibile anche il fantastico, creando quell’iperrealismo che permette all’assurdo di apparire naturale.

Quando si osservano i bigrammi distintivi per romanzo, emergono identità narrative molto nette. Norwegian Wood è l’unico testo in cui compaiono con forza relazioni familiari e riferimenti quotidiani concreti( negozi di dischi, domeniche mattina, figure paterne). 1Q84, al contrario, costruisce universi simbolici autonomi, popolati da personaggi-metafora e oggetti magici (La ripetizione ossessiva di “fuka eri” riflette il suo ruolo di enigma irrisolvibile attorno a cui ruota la narrazione). Dance Dance Dance ruota ossessivamente attorno a un unico spazio, l’hotel, che diventa un labirinto psicologico, una metafora dell’inconscio.

I trigrammi rafforzano questa impressione. Murakami utilizza vere e proprie frasi-pattern che condensano personaggi, oggetti e concetti. Alter ego come boy named Crow, soprannomi pop come yellow submarine boy, o descrizioni iper-specifiche come white Subaru Forester mostrano una scrittura che combina cultura pop, precisione maniacale e simbolismo. Anche qui il gesto corporeo ritorna come forma primaria di comunicazione, Fuka-Eri che annuisce o scuote la testa rappresenta una soggettività che si esprime senza parole.

I grafi delle co-occorrenze mostrano quali parole compaiono frequentemente insieme nello stesso contesto. I testi realistici, come Norwegian Wood, presentano grafi più lineari e sparsi, mentre quelli fantastici (1Q84) generano reti dense, multi-centrate, quasi labirintiche. In tutti i casi, però, emerge un core lessicale condiviso (tempo, sguardo, corpo, mondo) attorno al quale si organizzano costellazioni specifiche di ogni romanzo.

L’analisi delle correlazioni tramite coefficiente phi consente di andare oltre la semplice frequenza misurando l’affinità statistica tra coppie di parole, cioè coppie di termini che tendono a comparire insieme in modo sistematico e non casuale. Norwegian Wood è dominato da correlazioni quotidiane e rituali, che ancorano la narrazione a un realismo emotivo. In Dance Dance Dance prevalgono legami spaziali e architettonici, con l’hotel come centro simbolico dell’alienazione urbana. 1Q84 mostra invece correlazioni simboliche e meta-narrative, dove nomi, oggetti e concetti costruiscono mondi autonomi e riflettono sulla natura stessa della finzione.

In questo contesto After Dark rappresenta un caso limite. Ambientato interamente in una sola notte, i grafi dei bigrammi sono frammentati. Non emergono luoghi mitici ricorrenti, né nomi ripetuti in modo ossessivo. After Dark è costruito come una sequenza di scene brevi, quasi cinematografiche, osservate da una “telecamera” impersonale.

La co-occorrenza delle parole rivela una forte coerenza tematica. Mari è l’unico vero hub del romanzo, è il punto di vista mobile che attraversa la notte e incontra personaggi diversi, ognuno dei quali resta confinato nel proprio episodio. Eri invece, pur essendo centrale sul piano simbolico, rimane periferica sul piano relazionale perché dorme. Il suo mondo è quello dell’inconscio ed mediato dalla televisione che rappresenta l’unico vero elemento fantastico del romanzo. La fortissima correlazione tra tv e screen conferma che la TV non è un oggetto, ma un portale tra realtà e altrove.

Un altro elemento dominante è il tempo. After Dark è scandito minuto per minuto: i numeri emergono isolati nei grafi, come se il tempo stesso fosse un personaggio silenzioso che sorveglia la narrazione. A differenza di 1Q84, dove il tempo è filosofico e metafisico, qui è materiale, implacabile, misurabile. Il risultato è una tensione costante tra movimento e immobilità. Mari vaga nella città, Eri resta immobile nel sonno.

Gli oggetti quotidiani – una chicken salad, una jacket pocket, un tavolo – assumono un peso sproporzionato, diventando ancore di realtà in un’atmosfera sospesa.

Le correlazioni sono deboli, disperse, episodiche. Non esiste una vera rete unificante, ma una serie di vignette notturne che si sfiorano senza fondersi.

3. ANALISI SENTIMENTALE

# 1. LEXICON DISPONIBILI

#   - bing: sentiment binario (positive/negative)
#   - AFINN: score numerico da -5 a +5
#   - nrc: 8 emozioni base + 2 poli di sentiment

get_sentiments("bing")
## # A tibble: 6,786 × 2
##    word        sentiment
##    <chr>       <chr>    
##  1 2-faces     negative 
##  2 abnormal    negative 
##  3 abolish     negative 
##  4 abominable  negative 
##  5 abominably  negative 
##  6 abominate   negative 
##  7 abomination negative 
##  8 abort       negative 
##  9 aborted     negative 
## 10 aborts      negative 
## # ℹ 6,776 more rows
get_sentiments("afinn")
## # A tibble: 2,477 × 2
##    word       value
##    <chr>      <dbl>
##  1 abandon       -2
##  2 abandoned     -2
##  3 abandons      -2
##  4 abducted      -2
##  5 abduction     -2
##  6 abductions    -2
##  7 abhor         -3
##  8 abhorred      -3
##  9 abhorrent     -3
## 10 abhors        -3
## # ℹ 2,467 more rows
get_sentiments("nrc")
## # A tibble: 13,872 × 2
##    word        sentiment
##    <chr>       <chr>    
##  1 abacus      trust    
##  2 abandon     fear     
##  3 abandon     negative 
##  4 abandon     sadness  
##  5 abandoned   anger    
##  6 abandoned   fear     
##  7 abandoned   negative 
##  8 abandoned   sadness  
##  9 abandonment anger    
## 10 abandonment fear     
## # ℹ 13,862 more rows
# 2. PAROLE DI GIOIA PER OGNI LIBRO

#parole di joy per il dataframe complessivo
nrc_joy <- get_sentiments("nrc") %>%
  filter(sentiment == "joy")

# Parole di gioia più frequenti per libro
libri_tidy %>%
  filter(libro %in% levels(libro)[1:8]) %>%
  inner_join(nrc_joy) %>%
  count(libro, word, sort = TRUE) %>%
  group_by(libro) %>%
  slice_max(n, n = 10) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, n, libro)) %>%
  ggplot(aes(word, n, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, scales = "free", ncol = 3) +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Most common joy words per book (NRC lexicon)",
       x = NULL,
       y = "Count")
## Joining with `by = join_by(word)`

libri_tidy %>%
  filter(libro %in% levels(libro)[9:15]) %>%
  inner_join(nrc_joy) %>%
  count(libro, word, sort = TRUE) %>%
  group_by(libro) %>%
  slice_max(n, n = 10) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, n, libro)) %>%
  ggplot(aes(word, n, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, scales = "free", ncol = 3) +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Most common joy words per book (NRC lexicon)",
       x = NULL,
       y = "Count")
## Joining with `by = join_by(word)`

# 3. SENTIMENT NEL TEMPO PER OGNI LIBRO
murakami_sentiment <- libri_tidy %>%
  inner_join(get_sentiments("bing")) %>%
  count(libro, index = linea %/% 80, sentiment) %>%
  pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>% 
  mutate(sentiment = positive - negative)
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 142662 of `x` matches multiple rows in `y`.
## ℹ Row 4530 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
murakami_sentiment
## # A tibble: 17 × 5
##    libro                                       index negative positive sentiment
##    <fct>                                       <dbl>    <int>    <int>     <int>
##  1 1-Hear the Wind Sing                            0      650      317      -333
##  2 2-Pinball, 1973                                 0      977      537      -440
##  3 3-A Wild Sheep Chase                            0     2184     1164     -1020
##  4 4-Hard Boiled Wonderland and the End of th…     0     3291     1696     -1595
##  5 5-Norwegian wood                                0     3041     2031     -1010
##  6 6-Dance Dance Dance                             0     3623     2160     -1463
##  7 7-South of the Border West of the Sun           0     1395      924      -471
##  8 8-The Wind-Up Bird Chronicle                    0     7091     3371     -3720
##  9 9-Sputnik Sweetheart                            0     1456     1107      -349
## 10 10-Kafka On The Shore                           0     4790     2628     -2162
## 11 11-After Dark                                   0     1160      637      -523
## 12 12-1Q84                                         0     9676     6324     -3352
## 13 12-1Q84                                         1      919      503      -416
## 14 13-Colorless Tsukuru Tazaki                     0     2025     1653      -372
## 15 14-Killing Commendatore                         0     5739     3742     -1997
## 16 15-The City and Its Uncertain                   0     3455     2345     -1110
## 17 15-The City and Its Uncertain                   1      128       85       -43
# Visualizzazione sentiment nel tempo per ogni libro
ggplot(murakami_sentiment, aes(index, sentiment, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, ncol = 3, scales = "free_x") +
  labs(title = "Sentiment over time per book (Bing lexicon)",
       x = "Section of book",
       y = "Sentiment (positive - negative)")

# 4. PAROLE CHE CONTRIBUISCONO MAGGIORMENTE AL SENTIMENT

bing_word_counts <- libri_tidy %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  ungroup()
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 142662 of `x` matches multiple rows in `y`.
## ℹ Row 4530 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
bing_word_counts
## # A tibble: 3,860 × 3
##    word    sentiment     n
##    <chr>   <chr>     <int>
##  1 hard    negative   1282
##  2 dark    negative    919
##  3 pretty  positive    801
##  4 lost    negative    787
##  5 bad     negative    714
##  6 strange negative    706
##  7 love    positive    701
##  8 cold    negative    673
##  9 top     positive    635
## 10 slowly  negative    597
## # ℹ 3,850 more rows
# Visualizzazione top 10 parole per sentiment nel corpus complessivo
bing_word_counts %>%
  group_by(sentiment) %>%
  top_n(10) %>%
  ungroup() %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~sentiment, scales = "free_y") +
  labs(title = "Words contributing most to sentiment — Murakami corpus",
       y = "Contribution to sentiment",
       x = NULL) +
  coord_flip()
## Selecting by n

# 5. PAROLE CHE CONTRIBUISCONO AL SENTIMENT PER LIBRO

libri_tidy %>%
  filter(libro %in% levels(libro)[1:8]) %>%
  inner_join(get_sentiments("bing")) %>%
  count(libro, word, sentiment, sort = TRUE) %>%
  group_by(libro, sentiment) %>%
  top_n(5) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, n, libro)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, scales = "free", ncol = 3) +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Top sentiment words per book",
       y = "Count",
       x = NULL)
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 142662 of `x` matches multiple rows in `y`.
## ℹ Row 4530 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
## Selecting by n

libri_tidy %>%
  filter(libro %in% levels(libro)[9:15]) %>%
  inner_join(get_sentiments("bing")) %>%
  count(libro, word, sentiment, sort = TRUE) %>%
  group_by(libro, sentiment) %>%
  top_n(5) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, n, libro)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, scales = "free", ncol = 3) +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Top sentiment words per book",
       y = "Count",
       x = NULL)
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 86992 of `x` matches multiple rows in `y`.
## ℹ Row 3857 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
## Selecting by n

# 6. CAPITOLI PIU' NEGATIVI PER OGNI LIBRO
# Definisco i libri da analizzare
libri_con_capitoli <- c(
  "1-Hear the Wind Sing",
  "2-Pinball, 1973", 
  "7-South of the Border West of the Sun",
  "4-Hard Boiled Wonderland and the End of the World",
  "9-Sputnik Sweetheart",
  "10-Kafka On The Shore",
  "14-Killing Commendatore"
)

# Crea dataset con colonna chapter 
libri_con_chapter <- dfM %>%
  filter(libro %in% libri_con_capitoli) %>%
  group_by(libro) %>%
  mutate(chapter = case_when(
    # Gruppo 1: Solo numero all'inizio (1, 2, 3...)
    libro %in% c("1-Hear the Wind Sing", 
                 "2-Pinball, 1973",
                 "4-Hard Boiled Wonderland and the End of the World",
                 "14-Killing Commendatore") ~ 
      cumsum(str_detect(text, regex("^[0-9]+", ignore_case = TRUE))),
    
    # Gruppo 2: Numero attaccato a parola (1My, 2In, 15I...)
    libro == "7-South of the Border West of the Sun" ~
      cumsum(str_detect(text, regex("^[0-9]+[A-Z]", ignore_case = FALSE))),
    
    # Gruppo 3: "CHAPTER" seguito da numero (CHAPTER 1, CHAPTER 16...)
    libro %in% c("9-Sputnik Sweetheart", "10-Kafka On The Shore") ~
      cumsum(str_detect(text, regex("^chapter [0-9]+", ignore_case = TRUE))),
    
    TRUE ~ 0
  )) %>%
  ungroup()

cat("\n=== Capitoli identificati per libro ===\n")
## 
## === Capitoli identificati per libro ===
libri_con_chapter %>%
  group_by(libro) %>%
  summarise(chapters = max(chapter)) %>%
  arrange(desc(chapters)) %>%
  print()
## # A tibble: 7 × 2
##   libro                                             chapters
##   <fct>                                                <dbl>
## 1 14-Killing Commendatore                                 64
## 2 10-Kafka On The Shore                                   49
## 3 1-Hear the Wind Sing                                    40
## 4 4-Hard Boiled Wonderland and the End of the World       40
## 5 2-Pinball, 1973                                         26
## 6 9-Sputnik Sweetheart                                    16
## 7 7-South of the Border West of the Sun                   15
# Tokenizza solo questi libri
libri_chapter_tidy <- libri_con_chapter %>%
  unnest_tokens(word, text) %>%
  anti_join(stop_words) %>%
  filter(chapter > 0)  
## Joining with `by = join_by(word)`
# Analisi capitoli più negativi
bingnegative <- get_sentiments("bing") %>%
  filter(sentiment == "negative")

# Conta parole totali per libro e capitolo
wordcounts <- libri_chapter_tidy %>%
  group_by(libro, chapter) %>%
  summarize(words = n(), .groups = "drop")

# Trova i capitoli più negativi per libro
capitoli_negativi <- libri_chapter_tidy %>%
  semi_join(bingnegative) %>%
  group_by(libro, chapter) %>%
  summarize(negativewords = n(), .groups = "drop") %>%
  left_join(wordcounts, by = c("libro", "chapter")) %>%
  mutate(ratio = negativewords / words) %>%
  group_by(libro) %>%
  slice_max(ratio, n = 1) %>%
  ungroup() %>%
  arrange(desc(ratio))
## Joining with `by = join_by(word)`
capitoli_negativi
## # A tibble: 7 × 5
##   libro                                       chapter negativewords words  ratio
##   <fct>                                         <dbl>         <int> <int>  <dbl>
## 1 1-Hear the Wind Sing                              6            17    72 0.236 
## 2 4-Hard Boiled Wonderland and the End of th…      32           101   651 0.155 
## 3 14-Killing Commendatore                          51           200  1508 0.133 
## 4 10-Kafka On The Shore                            29            93   771 0.121 
## 5 2-Pinball, 1973                                  15            51   476 0.107 
## 6 7-South of the Border West of the Sun            13            70   711 0.0985
## 7 9-Sputnik Sweetheart                             11            99  1116 0.0887
# Visualizzazione capitoli più negativi
capitoli_negativi %>%
  mutate(libro = fct_reorder(libro, ratio)) %>%
  ggplot(aes(libro, ratio, fill = libro)) +
  geom_col(show.legend = FALSE) +
  geom_text(aes(label = paste("Ch.", chapter)), hjust = -0.1, size = 3) +
  coord_flip() +
  labs(title = "Most negative chapter per book",
       subtitle = "Only books with identifiable chapter structure",
       x = NULL,
       y = "Ratio of negative words") +
  theme_bw()

# 7. SENTIMENT MEDIO PER LIBRO (AFINN)

libri_tidy %>%
  inner_join(get_sentiments("afinn")) %>%
  group_by(libro) %>%
  summarize(mean_sentiment = mean(value)) %>%
  arrange(mean_sentiment) %>%
  mutate(libro = fct_reorder(libro, mean_sentiment)) %>%
  ggplot(aes(libro, mean_sentiment, fill = mean_sentiment > 0)) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  labs(title = "Average sentiment per book (AFINN lexicon)",
       x = NULL,
       y = "Mean sentiment score")
## Joining with `by = join_by(word)`

L’analisi delle joy words basata sul lexicon NRC mostra con chiarezza che nei testi di Murakami, la felicità non è mai un’emozione esplicitamente dichiarata, ma anzi come un concetto debole, indiretto e ambiguo. Le parole classificate come “gioiose” non rimandano a stati di felicità pienamente vissuti, ma a qualità estetiche, gesti minimi, momenti sospesi o forme di accettazione passiva dell’esperienza.

La caratteristica però non è tanto la poca presenza di termini positivi, quanto l’assenza sistematica del termine “felicità” esplicito. Parole come happy, happiness o joy risultano rare o del tutto assenti dalle posizioni più frequenti, confermando che Murakami evita consapevolmente la nominazione diretta della felicità. Questo dato è coerente con la sua poetica, che oscilla stabilmente tra neutralità emotiva, malinconia e oscurità, senza mai approdare a una gioia pienamente affermata.

Le parole ricorrenti identificate come “gioiose” (pretty, love, smile, fine) hanno una bassa intensità emotiva, un forte ancoraggio al corpo o al quotidiano e una totale assenza di euforia. Si tratta di termini che descrivono stati lievi, momentanei, spesso privi di una dimensione trasformativa. Questo suggerisce che la gioia, nel corpus murakamiano, sia attenuata, transitoria e frequentemente retrospettiva (un residuo fragile di un’esperienza già perduta).

Questa concezione emerge con particolare chiarezza in Norwegian Wood, il romanzo che statisticamente contiene il maggior numero di parole di gioia (ma che non è il più positivo). Qui love (con oltre 100 occorrenze) domina il lessico positivo, insieme a pretty e beautiful, cosa che però entra in completa contraddizione con il tono complessivo del romanzo che resta profondamente triste. Si parla d’amore mentre tutto va in pezzi. in questo contesto love non è promessa di felicità ma accompagna il trauma (suicidi, depressione, lutto) ed è considerato come un preludio alla perdita.

In 1Q84, invece, pretty domina il lessico (circa 250 occorrenze), soprattutto in riferimento a Fuka-Eri, figura bellissima e inafferrabile, mentre love convive con parole come friend e fine. Non c’è passione travolgente, ma una felicità possibile, costruita lentamente, quasi con diffidenza. Questo tra l’altro è l’unico romanzo che mostra una vera tendenza positiva finale concludendo con il ricongiungimento di Tengo e Aomame. La dinamica può essere vista come una sorta di fiaba adulta dopo centinaia di pagine di oscurità.

Al polo opposto si colloca The Wind-Up Bird Chronicle, dove le parole di gioia sono decisamente poche rispetto alla lunghezza del testo (max ~150). Qui la violenza storica, il trauma e la discesa nell’inconscio sovrastano qualsiasi spiraglio luminoso. Il sentimento resta costantemente negativo, confermando che si tratta del romanzo più cupo e disturbante di Murakami. Anche quando compaiono termini positivi, questi vengono assorbiti da un contesto di guerra, perdita e disintegrazione personale.

Guardando l’andamento del sentiment nel tempo, tutti i romanzi mostrano un sentiment che fluttua tra -0.25 e +0.25, raramente superando questi limiti e questo può essere visto come un assenza sistematica di ambiguità emotiva. Luce e ombra coesistono senza mai annullarsi. After Dark, ad esempio, resta quasi neutro per tutta la sua durata e infatti può essere considerato come un romanzo che osserva, non giudica, che registra la notte senza attribuirle un significato morale.

Le parole negative più frequenti – hard, dark, lost, strange – definiscono il cuore emotivo dell’opera murakamiana. hard (≈1280 occorrenze) indica difficoltà esistenziale, non male morale. La vita, nei suoi romanzi, non è malvagia, è difficile. Non c’è un nemico da combattere, ma una fatica continua dell’esistere. La coppia dark/darkness (≈1495 occorrenze) domina il lessico negativo, confermando l’ossessione per l’oscurità fisica e metaforica. Il rapporto 2:1 con light segnala una asimmetria strutturale. Lost esprime lo stato permanente dei protagonisti, sempre smarriti, senza mappe né direzioni chiare. Strange, infine, normalizza il surreale infatti di fronte a due lune nel cielo o a gatti che parlano, i personaggi non reagiscono con terrore, ma con un semplice “è strano”, accettando l’anomalia come parte del reale.

Le parole positive risultano profondamente ambigue. Termini come quiet o silent, classificati come positivi dai dizionari, in Murakami assumono spesso una tonalità inquietante. Il silenzio non è pace, ma vuoto; la quiete può essere sospensione prima di qualcosa di oscuro.

Nel complesso, la classifica finale del sentiment medio conferma che Murakami non scrive mai romanzi davvero felici. Anche il più “positivo”, Colorless Tsukuru Tazaki, supera di poco lo zero, suggerendo non tanto una gioia piena quanto una riconciliazione malinconica con il passato. Dai primi romanzi nichilisti fino alle opere più mature, l’autore sembra spostarsi non verso la felicità, ma verso una forma più gentile di tristezza.

Murakami rappresenta un equilibrio instabile tra luce e ombra, in cui la felicità non è mai gridata, ma sussurrata, spesso proprio nei momenti in cui sta per svanire.

4. NETWORK ANALYSIS

# COSTRUZIONE DELLA RETE GLOBALE

cat("\n=== 1. GLOBAL LEXICAL NETWORK CONSTRUCTION ===\n\n")
## 
## === 1. GLOBAL LEXICAL NETWORK CONSTRUCTION ===
# Calcola co-occorrenze doppie (pairwise count)
word_pairs <- section_words %>%
  pairwise_count(word, section, sort = TRUE) %>%
  filter(n > 100) 

cat("Total word pairs (n > 100):", nrow(word_pairs), "\n")
## Total word pairs (n > 100): 158
cat("Sample pairs:\n")
## Sample pairs:
print(head(word_pairs, 10))
## # A tibble: 10 × 3
##    item1  item2      n
##    <chr>  <chr>  <dbl>
##  1 fuka   eri      606
##  2 eri    fuka     606
##  3 shook  head     495
##  4 head   shook    495
##  5 closed eyes     339
##  6 eyes   closed   339
##  7 tengo  eri      308
##  8 eri    tengo    308
##  9 fuka   tengo    304
## 10 tengo  fuka     304
# Crea grafo non diretto
g <- word_pairs %>%
  as_tbl_graph(directed = FALSE)

cat("\nNetwork size:\n")
## 
## Network size:
cat("- Nodes (words):", vcount(g), "\n")
## - Nodes (words): 89
cat("- Edges (co-occurrences):", ecount(g), "\n")
## - Edges (co-occurrences): 158
cat("- Density:", round(edge_density(g), 4), "\n\n")
## - Density: 0.0403
#calcolo di tutte le componenti presenti
cat("\n=== 2. CONNECTED COMPONENTS ===\n\n")
## 
## === 2. CONNECTED COMPONENTS ===
components_list <- components(g)

cat("Graph connectivity:\n")
## Graph connectivity:
cat("- Number of components:", components_list$no, "\n")
## - Number of components: 18
cat("- Size of largest component:", max(components_list$csize), "\n")
## - Size of largest component: 52
cat("- All component sizes:", components_list$csize, "\n\n")
## - All component sizes: 52 2 2 2 2 3 2 2 2 2 2 2 2 4 2 2 2 2
giant_component <- induced_subgraph(g, which(components_list$membership == which.max(components_list$csize)))

cat("Largest connected component:\n")
## Largest connected component:
cat("- Nodes:", vcount(giant_component), "\n")
## - Nodes: 52
cat("- Edges:", ecount(giant_component), "\n")
## - Edges: 118
cat("- Density:", round(edge_density(giant_component), 4), "\n\n")
## - Density: 0.089
# 2. DEGREE CENTRALITY

cat("\n=== 2. DEGREE CENTRALITY ===\n\n")
## 
## === 2. DEGREE CENTRALITY ===
degree_igraph <- degree(g)

# Crea dataframe risultati
degree_df <- tibble(
  word = V(g)$name,
  degree = degree_igraph
) %>%
  arrange(desc(degree))

cat("Top 20 words by degree centrality:\n")
## Top 20 words by degree centrality:
print(degree_df %>% head(20))
## # A tibble: 20 × 2
##    word   degree
##    <chr>   <dbl>
##  1 time       68
##  2 tengo      20
##  3 eyes       12
##  4 aomame      8
##  5 world       8
##  6 head        6
##  7 people      6
##  8 looked      6
##  9 fuka        4
## 10 eri         4
## 11 left        4
## 12 hand        4
## 13 kano        4
## 14 night       4
## 15 deep        4
## 16 middle      4
## 17 book        4
## 18 read        4
## 19 shook       2
## 20 closed      2
# Visualizzazione
degree_df %>%
  head(20) %>%
  mutate(word = reorder(word, degree)) %>%
  ggplot(aes(word, degree)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(title = "Degree Centrality — Murakami Lexical Network",
       subtitle = "Most connected words (lexical hubs)",
       x = NULL, y = "Degree (number of co-occurring words)") +
  theme_minimal()

# 3. CLOSENESS CENTRALITY

cat("\n\n=== 3. CLOSENESS CENTRALITY ===\n\n")
## 
## 
## === 3. CLOSENESS CENTRALITY ===
closeness_scores <- closeness(giant_component)

closeness_df <- tibble(
  word = V(giant_component)$name,
  closeness = closeness_scores
) %>%
  arrange(desc(closeness))


cat("Top 20 words by closeness centrality:\n")
## Top 20 words by closeness centrality:
print(closeness_df %>% head(20))
## # A tibble: 20 × 2
##    word   closeness
##    <chr>      <dbl>
##  1 time     0.0143 
##  2 tengo    0.00971
##  3 looked   0.00926
##  4 eyes     0.00917
##  5 aomame   0.00909
##  6 world    0.00901
##  7 people   0.00893
##  8 head     0.00862
##  9 night    0.00862
## 10 left     0.00840
## 11 hand     0.00840
## 12 day      0.00833
## 13 life     0.00833
## 14 mind     0.00833
## 15 told     0.00833
## 16 ago      0.00833
## 17 feel     0.00833
## 18 inside   0.00833
## 19 passed   0.00833
## 20 lot      0.00833
# Visualizzazione
closeness_df %>%
  head(20) %>%
  mutate(word = reorder(word, closeness)) %>%
  ggplot(aes(word, closeness)) +
  geom_col(fill = "coral") +
  coord_flip() +
  labs(title = "Closeness Centrality — Murakami Lexical Network",
       subtitle = "Words with shortest average distance to others",
       x = NULL, y = "Closeness") +
  theme_minimal()

# 4. BETWEENNESS CENTRALITY

cat("\n\n=== 4. BETWEENNESS CENTRALITY ===\n\n")
## 
## 
## === 4. BETWEENNESS CENTRALITY ===
betweenness_scores <- betweenness(g)

betweenness_df <- tibble(
  word = V(g)$name,
  betweenness = betweenness_scores
) %>%
  arrange(desc(betweenness))

cat("Top 20 words by betweenness centrality:\n")
## Top 20 words by betweenness centrality:
print(betweenness_df %>% head(20))
## # A tibble: 20 × 2
##    word      betweenness
##    <chr>           <dbl>
##  1 time            1158.
##  2 tengo            246.
##  3 eyes             239 
##  4 head              99 
##  5 aomame            99 
##  6 night             98 
##  7 world             50 
##  8 deep              50 
##  9 middle            50 
## 10 looked            18 
## 11 book               2 
## 12 read               2 
## 13 kano               1 
## 14 fuka               0 
## 15 eri                0 
## 16 shook              0 
## 17 closed             0 
## 18 chrysalis          0 
## 19 air                0 
## 20 bird               0
# Visualizzazione
betweenness_df %>%
  head(20) %>%
  mutate(word = reorder(word, betweenness)) %>%
  ggplot(aes(word, betweenness)) +
  geom_col(fill = "darkgreen") +
  coord_flip() +
  labs(title = "Betweenness Centrality — Murakami Lexical Network",
       subtitle = "Words that bridge different semantic regions",
       x = NULL, y = "Betweenness") +
  theme_minimal()

# Combina tutte le centralità
centrality_all <- degree_df %>%
  left_join(closeness_df, by = "word") %>%
  left_join(betweenness_df, by = "word")


# 5. NETWORK VISUALIZATION

cat("\n\n=== 5. NETWORK VISUALIZATION ===\n\n")
## 
## 
## === 5. NETWORK VISUALIZATION ===
set.seed(42)
coords <- layout_with_fr(g)

# Plot colorato per degree
plot(g, layout = coords,
     vertex.size = sqrt(degree(g)) * 1.5,
     vertex.color = "lightblue",
     vertex.label = ifelse(degree(g) > quantile(degree(g), 0.95), 
                           V(g)$name, NA),
     vertex.label.cex = 0.7,
     vertex.label.color = "black",
     edge.width = 0.3,
     edge.color = "gray80",
     main = "Murakami Lexical Network — Node size = Degree")

# Plot con ggraph
set.seed(42)
ggraph(g, layout = "fr") +
  geom_edge_link(alpha = 0.1, color = "gray70") +
  geom_node_point(aes(size = degree(g), color = betweenness(g)), alpha = 0.7) +
  geom_node_text(aes(label = name, 
                     filter = degree(g) > quantile(degree(g), 0.95)),
                 repel = TRUE, size = 3, max.overlaps = 20) +
  scale_color_viridis_c(option = "plasma") +
  scale_size_continuous(range = c(1, 8)) +
  theme_void() +
  labs(title = "Murakami Lexical Network",
       subtitle = "Node size = Degree | Color = Betweenness",
       color = "Betweenness", size = "Degree")

# 6. COMMUNITY DETECTION

cat("\n\n=== 6. COMMUNITY DETECTION ===\n\n")
## 
## 
## === 6. COMMUNITY DETECTION ===
# Modularity matrix
m <- ecount(g)
k <- degree(g)
n <- vcount(g)

A <- as_adjacency_matrix(g, sparse = FALSE)

B <- A - (k %*% t(k)) / (2 * m)

cat("Modularity matrix properties:\n")
## Modularity matrix properties:
cat("- Sum of row 1:", round(sum(B[1,]), 10), "(should be ~0)\n")
## - Sum of row 1: 0 (should be ~0)
cat("- Sum of all elements:", round(sum(B), 10), "(should be ~0)\n\n")
## - Sum of all elements: 0 (should be ~0)
# Community detection con Louvain
communities <- cluster_louvain(g)

cat("Community detection results:\n")
## Community detection results:
cat("- Number of communities:", length(communities), "\n")
## - Number of communities: 22
cat("- Modularity Q:", round(modularity(communities), 4), "\n")
## - Modularity Q: 0.6781
cat("- Community sizes:", sizes(communities), "\n\n")
## - Community sizes: 12 3 7 2 27 2 2 2 3 2 3 2 2 2 2 2 2 4 2 2 2 2
# Top parole per community
communities_df <- tibble(
  word = V(g)$name,
  community = membership(communities),
  degree = degree(g)
) %>%
  group_by(community) %>%
  arrange(desc(degree)) %>%
  slice_head(n = 8)

cat("Top 8 words per community:\n")
## Top 8 words per community:
print(communities_df, n = 100)
## # A tibble: 66 × 3
## # Groups:   community [22]
##    word       community  degree
##    <chr>      <membrshp>  <dbl>
##  1 tengo       1             20
##  2 aomame      1              8
##  3 world       1              8
##  4 people      1              6
##  5 fuka        1              4
##  6 eri         1              4
##  7 komatsu     1              2
##  8 dowager     1              2
##  9 head        2              6
## 10 shook       2              2
## 11 shake       2              2
## 12 eyes        3             12
## 13 looked      3              6
## 14 deep        3              4
## 15 closed      3              2
## 16 breath      3              2
## 17 narrowed    3              2
## 18 shut        3              2
## 19 chrysalis   4              2
## 20 air         4              2
## 21 time        5             68
## 22 left        5              4
## 23 hand        5              4
## 24 day         5              2
## 25 life        5              2
## 26 mind        5              2
## 27 told        5              2
## 28 ago         5              2
## 29 bird        6              2
## 30 wind        6              2
## 31 saeki       7              2
## 32 miss        7              2
## 33 wataya      8              2
## 34 noboru      8              2
## 35 kano        9              4
## 36 creta       9              2
## 37 malta       9              2
## 38 hoshino    10              2
## 39 nakata     10              2
## 40 night      11              4
## 41 middle     11              4
## 42 aged       11              2
## 43 cup        12              2
## 44 coffee     12              2
## 45 tomohiko   13              2
## 46 amada      13              2
## 47 front      14              2
## 48 door       14              2
## 49 phone      15              2
## 50 call       15              2
## 51 miu        16              2
## 52 sumire     16              2
## 53 shoulder   17              2
## 54 bag        17              2
## 55 book       18              4
## 56 read       18              4
## 57 reading    18              2
## 58 books      18              2
## 59 elementary 19              2
## 60 school     19              2
## 61 dolphin    20              2
## 62 hotel      20              2
## 63 clouds     21              2
## 64 sky        21              2
## 65 minutes    22              2
## 66 ten        22              2
# Visualizzazione communities
set.seed(42)
ggraph(g, layout = "fr") +
  geom_edge_link(alpha = 0.05, color = "gray80") +
  geom_node_point(aes(color = factor(membership(communities)), 
                      size = degree(g)), alpha = 0.7) +
  geom_node_text(aes(label = name, 
                     filter = degree(g) > quantile(degree(g), 0.93)),
                 repel = TRUE, size = 3, max.overlaps = 15) +
  scale_size_continuous(range = c(2, 8)) +
  theme_void() +
  labs(title = "Semantic Communities — Murakami Corpus",
       subtitle = paste("Louvain algorithm | Modularity Q =", 
                       round(modularity(communities), 3))) +
  theme(legend.position = "none")

# 7. PROPRIETà PICCOLO MONDO

cat("\n\n=== 7. SMALL-WORLD PROPERTIES ===\n\n")
## 
## 
## === 7. SMALL-WORLD PROPERTIES ===
mean_dist <- mean_distance(giant_component, directed = FALSE)
diam <- diameter(giant_component)
n_nodes <- vcount(giant_component)

cat("Network properties:\n")
## Network properties:
cat("- Nodes:", n_nodes, "\n")
## - Nodes: 52
cat("- Mean distance:", round(mean_dist, 3), "\n")
## - Mean distance: 2.59
cat("- Diameter:", diam, "\n")
## - Diameter: 6
cat("- log(n):", round(log(n_nodes), 3), "\n")
## - log(n): 3.951
cat("- Mean distance / log(n):", round(mean_dist / log(n_nodes), 3), "\n\n")
## - Mean distance / log(n): 0.655
dist_table <- distance_table(giant_component)
dist_freq <- dist_table$res / sum(dist_table$res)

barplot(dist_freq, 
        names.arg = 1:length(dist_freq),
        main = "Distribution of Distances — Largest Component",
        xlab = "Distance", ylab = "Frequency",
        col = "steelblue")

cat("avg distance ~", 
    round(mean_dist, 2), ", \n")
## avg distance ~ 2.59 ,
# 9. ANALISI COMPONENTI ISOLATE

cat("\n\n=== 9. ISOLATED COMPONENTS ===\n\n")
## 
## 
## === 9. ISOLATED COMPONENTS ===
for (i in 1:components_list$no) {
  if (components_list$csize[i] < max(components_list$csize)) {
    cat("\nComponent", i, "(size:", components_list$csize[i], ")\n")
    comp_nodes <- V(g)$name[components_list$membership == i]
    cat("Nodes:", paste(comp_nodes, collapse = ", "), "\n")
    
    comp_graph <- induced_subgraph(g, which(components_list$membership == i))
    
    if (ecount(comp_graph) > 0) {
      edges_comp <- as_data_frame(comp_graph, what = "edges")
      cat("Edges:\n")
      print(edges_comp)
    }
  }
}
## 
## Component 2 (size: 2 )
## Nodes: chrysalis, air 
## Edges:
##        from  to   n
## 1 chrysalis air 280
## 2 chrysalis air 280
## 
## Component 3 (size: 2 )
## Nodes: bird, wind 
## Edges:
##   from   to   n
## 1 bird wind 233
## 2 bird wind 233
## 
## Component 4 (size: 2 )
## Nodes: saeki, miss 
## Edges:
##    from   to   n
## 1 saeki miss 176
## 2 saeki miss 176
## 
## Component 5 (size: 2 )
## Nodes: wataya, noboru 
## Edges:
##     from     to   n
## 1 wataya noboru 160
## 2 wataya noboru 160
## 
## Component 6 (size: 3 )
## Nodes: creta, kano, malta 
## Edges:
##    from    to   n
## 1 creta  kano 156
## 2 creta  kano 156
## 3  kano malta 110
## 4  kano malta 110
## 
## Component 7 (size: 2 )
## Nodes: hoshino, nakata 
## Edges:
##      from     to   n
## 1 hoshino nakata 154
## 2 hoshino nakata 154
## 
## Component 8 (size: 2 )
## Nodes: cup, coffee 
## Edges:
##   from     to   n
## 1  cup coffee 141
## 2  cup coffee 141
## 
## Component 9 (size: 2 )
## Nodes: tomohiko, amada 
## Edges:
##       from    to   n
## 1 tomohiko amada 125
## 2 tomohiko amada 125
## 
## Component 10 (size: 2 )
## Nodes: front, door 
## Edges:
##    from   to   n
## 1 front door 122
## 2 front door 122
## 
## Component 11 (size: 2 )
## Nodes: phone, call 
## Edges:
##    from   to   n
## 1 phone call 117
## 2 phone call 117
## 
## Component 12 (size: 2 )
## Nodes: miu, sumire 
## Edges:
##   from     to   n
## 1  miu sumire 116
## 2  miu sumire 116
## 
## Component 13 (size: 2 )
## Nodes: shoulder, bag 
## Edges:
##       from  to   n
## 1 shoulder bag 110
## 2 shoulder bag 110
## 
## Component 14 (size: 4 )
## Nodes: book, read, reading, books 
## Edges:
##   from      to   n
## 1 book    read 109
## 2 book    read 109
## 3 book reading 103
## 4 book reading 103
## 5 read   books 102
## 6 read   books 102
## 
## Component 15 (size: 2 )
## Nodes: elementary, school 
## Edges:
##         from     to   n
## 1 elementary school 107
## 2 elementary school 107
## 
## Component 16 (size: 2 )
## Nodes: dolphin, hotel 
## Edges:
##      from    to   n
## 1 dolphin hotel 105
## 2 dolphin hotel 105
## 
## Component 17 (size: 2 )
## Nodes: clouds, sky 
## Edges:
##     from  to   n
## 1 clouds sky 103
## 2 clouds sky 103
## 
## Component 18 (size: 2 )
## Nodes: minutes, ten 
## Edges:
##      from  to   n
## 1 minutes ten 102
## 2 minutes ten 102

Per analizzare l’organizzazione strutturale del vocabolario murakamiano, abbiamo utilizzato la rete di co-occorrenze lessicali. La rete risultante comprende 89 nodi e 158 archi con una densità pari a 0.0403. Tale bassa densità indica che il lessico non forma una struttura uniformemente connessa, ma tende a organizzarsi in nuclei tematici distinti, separati da ampie regioni scarsamente collegate. La rete appare dunque globalmente sparsa, ma localmente densa.

La rete è composta da 18 componenti connesse, una delle quali emerge nettamente come componente gigante, con 52 nodi e 118 archi. Questa componente concentra la maggior parte delle connessioni e rappresenta il nucleo semantico globale del corpus che come abbiamo visto anche dall’analisi precedenti gira tutto intorno alla parola Time. Le restanti componenti sono di dimensioni molto ridotte (2–4 nodi) e risultano strutturalmente isolate dal resto della rete.

La densità della componente gigante (0.089) è più che doppia rispetto a quella dell’intera rete, segnalando una maggiore coesione interna.

La degree centrality identifica le parole che co-occorrono con il maggior numero di termini distinti, fungendo da hub lessicali.

La parola time emerge come hub assoluto, con un degree pari a 68, valore nettamente superiore a quello di qualsiasi altro nodo. Seguono tengo (20) ed eyes (12), mentre aomame, world, people, head e looked completano il nucleo delle parole più connesse. Questo insieme delinea un asse semantico stabile, incentrato su tempo, percezione, corpo ed esistenza, che come anche visto prima, attraversa trasversalmente l’intero corpus.

La closeness centrality misura quanto una parola sia mediamente vicina a tutte le altre in termini di distanza geodetica. Poiché la rete globale è sconnessa, la closeness è stata calcolata esclusivamente sulla componente gigante, evitando distorsioni dovute alla presenza di componenti isolate.

I risultati confermano time come nodo strutturalmente centrale anche in termini di distanza, La convergenza tra degree e closeness rafforza l’interpretazione di time come asse portante dell’architettura lessicale.

La betweenness centrality quantifica il numero di cammini minimi che attraversano un nodo, identificando le parole che fungono da ponti tra regioni semantiche diverse. Anche in questo caso, time domina nettamente con un valore pari a 1158, più del quadruplo del secondo classificato (tengo, 246).

Parole come eyes, head, night e world mostrano betweenness elevata pur avendo un degree relativamente contenuto, indicando che la loro importanza non risiede nel numero di connessioni, ma nella loro posizione strategica all’interno della rete. Al contrario, coppie di nomi propri come fuka e eri presentano betweenness nulla, rimanendo confinate in regioni semantiche locali senza contribuire alla connessione globale del grafo.

L’algoritmo di Louvain individua 22 comunità, con una modularità pari a 0.6781, valore che indica una struttura comunitaria fortemente marcata. Le connessioni all’interno delle comunità superano ampiamente quelle attese per caso, confermando la presenza di cluster semantici coerenti.

La comunità dominante è centrata su time e raccoglie termini esistenziali e temporali (life, mind, day, ago), configurandosi come il cuore filosofico del vocabolario. Altre comunità risultano fortemente legate a singoli romanzi o contesti narrativi, come il cluster di 1Q84 (tengo, aomame, fuka, eri), o il cluster percettivo dominato da eyes e dai verbi dello sguardo. Micro-comunità isolate, come book–read–reading–books, confermano la presenza di temi specifici e autosufficienti.

L’analisi delle proprietà di small-world, condotta sulla componente gigante, mostra una distanza media pari a 2.59 e un diametro pari a 6, a fronte di un valore log(𝑛)log(n) di circa 3.95. Il rapporto tra distanza media e log(𝑛)log(n) (≈ 0.65) indica che la rete presenta cammini sorprendentemente brevi.

Questo risultato suggerisce che il nucleo lessicale murakamiano è altamente navigabile. La distribuzione delle distanze mostra una netta concentrazione su valori bassi (2–3), tipica delle reti small-world reali.

Nelle componenti esterne si osservano numerose coppie di nomi propri (saeki–miss, wataya–noboru, hoshino–nakata, miu–sumire), che riflettono relazioni narrative specifiche e circoscritte.

L’analisi di rete mostra che il vocabolario murakamiano si organizza come una struttura small-world, caratterizzata da un nucleo compatto e altamente connesso dominato da poche parole-chiave (time, eyes, world), e da una costellazione di micro-comunità narrative isolate. La modularità elevata indica che ogni romanzo conserva una propria identità lessicale, pur condividendo un linguaggio concettuale trasversale.

In questo senso, la rete lessicale non è una semplice collezione di frequenze, ma una vera architettura semantica: il tempo, la percezione e l’esistenza fungono da assi portanti, mentre personaggi e contesti specifici formano isole narrative che arricchiscono, senza frammentare, l’universo murakamiano.

5. CONFRONTO TRA LIBRI

# PREPARAZIONE Tokenizzazione libri di confronto
altri_tidy <- dfA %>%
  unnest_tokens(word, text) %>%
  anti_join(stop_words) 
## Joining with `by = join_by(word)`
#write_rds(altri_tidy, "others_books_tidy.rds")


# 1. CONFRONTO MURAKAMI VS ALTRI AUTORI 

frequency <- bind_rows(
  libri_tidy %>% mutate(author = "Murakami"),
  altri_tidy %>% mutate(author = libro)
) %>%
  mutate(word = str_extract(word, "[a-z']+")) %>% 
  count(author, word) %>%
  group_by(author) %>%
  mutate(proportion = n / sum(n)) %>%
  select(-n) %>%
  pivot_wider(names_from = author, values_from = proportion)

frequency
## # A tibble: 35,676 × 6
##    word             `1984`    Murakami `The Great Gatsby` `The Long Goodbye`
##    <chr>             <dbl>       <dbl>              <dbl>              <dbl>
##  1 aaronson      0.000226  NA                  NA                         NA
##  2 aback         0.0000565  0.0000338          NA                         NA
##  3 abandon       0.0000847  0.0000476           0.0000590                 NA
##  4 abandoned     0.000113   0.000177            0.000118                  NA
##  5 abasement     0.0000565 NA                  NA                         NA
##  6 abashed       0.0000282 NA                  NA                         NA
##  7 abbreviated   0.0000282  0.0000107          NA                         NA
##  8 abbreviating  0.0000565  0.00000154         NA                         NA
##  9 abbreviations 0.0000847 NA                  NA                         NA
## 10 aberrations   0.0000282 NA                  NA                         NA
## # ℹ 35,666 more rows
## # ℹ 1 more variable: `Trout Fishing in America` <dbl>
# Plot e correlazione per ogni autore di confronto
libri_confronto <- setdiff(names(frequency), c("word", "Murakami"))

for (libro in libri_confronto) {
  
  p <- ggplot(frequency, aes(x = .data[[libro]], y = Murakami)) +
    geom_abline(color = "gray40", lty = 2) +
    geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
    scale_x_log10(labels = percent_format()) +
    scale_y_log10(labels = percent_format()) +
    labs(title = paste("Murakami vs", libro),
         x = libro,
         y = "Murakami") +
    theme_bw()
  
  print(p)
  
  cor_result <- cor.test(frequency[[libro]], frequency$Murakami)
  cat("\nCorrelation Murakami -", libro, ":", round(cor_result$estimate, 3), "\n")
}
## Warning: Removed 28918 rows containing missing values or values outside the scale range
## (`geom_text()`).

## 
## Correlation Murakami - 1984 : 0.586
## Warning: Removed 31140 rows containing missing values or values outside the scale range
## (`geom_text()`).

## 
## Correlation Murakami - The Great Gatsby : 0.54
## Warning: Removed 29710 rows containing missing values or values outside the scale range
## (`geom_text()`).

## 
## Correlation Murakami - The Long Goodbye : 0.705
## Warning: Removed 32092 rows containing missing values or values outside the scale range
## (`geom_text()`).

## 
## Correlation Murakami - Trout Fishing in America : 0.371
# 2. CONFRONTI DIRETTI: 1Q84 vs 1984

freq_1q84_vs_1984 <- bind_rows(
  libri_tidy %>%
    filter(libro == "12-1Q84") %>%
    mutate(author = "1Q84"),
  altri_tidy %>%
    filter(libro == "1984") %>%
    mutate(author = "1984")
) %>%
  mutate(word = str_extract(word, "[a-z']+")) %>%
  count(author, word) %>%
  group_by(author) %>%
  mutate(proportion = n / sum(n)) %>%
  select(-n) %>%
  pivot_wider(names_from = author, values_from = proportion)

# Plot
ggplot(freq_1q84_vs_1984, aes(x = `1984`, y = `1Q84`)) +
  geom_abline(color = "gray40", lty = 2) +
  geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
  scale_x_log10(labels = percent_format()) +
  scale_y_log10(labels = percent_format()) +
  labs(title = "1Q84 (Murakami) vs 1984 (Orwell)",
       x = "1984 — Orwell",
       y = "1Q84 — Murakami") +
  theme_bw()
## Warning: Removed 12876 rows containing missing values or values outside the scale range
## (`geom_text()`).

# Correlazione
cor.test(freq_1q84_vs_1984$`1984`, freq_1q84_vs_1984$`1Q84`)
## 
##  Pearson's product-moment correlation
## 
## data:  freq_1q84_vs_1984$`1984` and freq_1q84_vs_1984$`1Q84`
## t = 50.677, df = 5191, p-value < 2.2e-16
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  0.5568283 0.5932306
## sample estimates:
##       cor 
## 0.5753143
# 3. CONFRONTI DIRETTI: VENTO vs TROTA
freq_vento_vs_trota <- bind_rows(
  libri_tidy %>%
    filter(libro == "1-Hear the Wind Sing") %>%
    mutate(author = "Hear the Wind Sing"),
  altri_tidy %>%
    filter(libro == "Trout Fishing in America") %>%  
    mutate(author = "Trout Fishing in America")    
) %>%
  mutate(word = str_extract(word, "[a-z']+")) %>%
  count(author, word) %>%
  group_by(author) %>%
  mutate(proportion = n / sum(n)) %>%
  select(-n) %>%
  pivot_wider(names_from = author, values_from = proportion)

# Plot - usa i nomi completi delle colonne
ggplot(freq_vento_vs_trota, aes(x = `Trout Fishing in America`, y = `Hear the Wind Sing`)) +
  geom_abline(color = "gray40", lty = 2) +
  geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
  scale_x_log10(labels = percent_format()) +
  scale_y_log10(labels = percent_format()) +
  labs(title = "Hear the Wind Sing vs Trout Fishing in America",
       x = "Trout Fishing in America — Brautigan",
       y = "Hear the Wind Sing — Murakami") +
  theme_bw()
## Warning: Removed 4766 rows containing missing values or values outside the scale range
## (`geom_text()`).

# Correlazione - usa i nomi completi
cor.test(freq_vento_vs_trota$`Trout Fishing in America`, 
         freq_vento_vs_trota$`Hear the Wind Sing`)
## 
##  Pearson's product-moment correlation
## 
## data:  freq_vento_vs_trota$`Trout Fishing in America` and freq_vento_vs_trota$`Hear the Wind Sing`
## t = 21.295, df = 1325, p-value < 2.2e-16
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  0.4637394 0.5439821
## sample estimates:
##      cor 
## 0.504951
# 4. IL GRANDE GATSBY vs TUTTI I LIBRI DI MURAKAMI

# Trova quale libro di Murakami è più simile al Grande Gatsby
risultati_gatsby <- tibble(libro = character(), correlazione = numeric())

libri_murakami <- unique(libri_tidy$libro)

for (l in libri_murakami) {
  
  freq_temp <- bind_rows(
    libri_tidy %>% filter(libro == l) %>% mutate(author = "Murakami"),
    altri_tidy %>% filter(libro == "The Great Gatsby") %>% mutate(author = "Gatsby")
  ) %>%
    mutate(word = str_extract(word, "[a-z']+")) %>%
    count(author, word) %>%
    group_by(author) %>%
    mutate(proportion = n / sum(n)) %>%
    select(-n) %>%
    pivot_wider(names_from = author, values_from = proportion) %>%
    filter(!is.na(Murakami), !is.na(Gatsby))
  
  cor_val <- cor(freq_temp$Gatsby, freq_temp$Murakami, use = "complete.obs")
  
  risultati_gatsby <- risultati_gatsby %>%
    add_row(libro = l, correlazione = cor_val)
}

# Trova libro più correlato
migliore_gatsby <- risultati_gatsby %>% 
  slice_max(correlazione, n = 1)

cat("\nMost correlated book to The Great Gatsby:", migliore_gatsby$libro,
    "— Correlation:", round(migliore_gatsby$correlazione, 3), "\n")
## 
## Most correlated book to The Great Gatsby: 8-The Wind-Up Bird Chronicle — Correlation: 0.67
# Plot del migliore
freq_plot_gatsby <- bind_rows(
  libri_tidy %>% filter(libro == migliore_gatsby$libro) %>% mutate(author = "Murakami"),
  altri_tidy %>% filter(libro == "The Great Gatsby") %>% mutate(author = "Gatsby")
) %>%
  mutate(word = str_extract(word, "[a-z']+")) %>%
  count(author, word) %>%
  group_by(author) %>%
  mutate(proportion = n / sum(n)) %>%
  select(-n) %>%
  pivot_wider(names_from = author, values_from = proportion)

ggplot(freq_plot_gatsby, aes(x = Gatsby, y = Murakami)) +
  geom_abline(color = "gray40", lty = 2) +
  geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
  scale_x_log10(labels = percent_format()) +
  scale_y_log10(labels = percent_format()) +
  labs(title = paste("The Great Gatsby vs", migliore_gatsby$libro),
       x = "The Great Gatsby — Fitzgerald",
       y = paste(migliore_gatsby$libro, "— Murakami")) +
  theme_bw()
## Warning: Removed 9661 rows containing missing values or values outside the scale range
## (`geom_text()`).

# 5. IL LUNGO ADDIO vs TUTTI I LIBRI DI MURAKAMI
risultati_addio <- tibble(libro = character(), correlazione = numeric())

for (l in libri_murakami) {
  
  freq_temp <- bind_rows(
    libri_tidy %>% filter(libro == l) %>% mutate(author = "Murakami"),
    altri_tidy %>% filter(libro == "The Long Goodbye") %>% mutate(author = "Addio")
  ) %>%
    mutate(word = str_extract(word, "[a-z']+")) %>%
    count(author, word) %>%
    group_by(author) %>%
    mutate(proportion = n / sum(n)) %>%
    select(-n) %>%
    pivot_wider(names_from = author, values_from = proportion) %>%
    filter(!is.na(Murakami), !is.na(Addio))
  
  cor_val <- cor(freq_temp$Addio, freq_temp$Murakami, use = "complete.obs")
  
  risultati_addio <- risultati_addio %>%
    add_row(libro = l, correlazione = cor_val)
}

# Trova libro più correlato
migliore_addio <- risultati_addio %>% 
  slice_max(correlazione, n = 1)

cat("\nMost correlated book to The Long Goodbye:", migliore_addio$libro,
    "— Correlation:", round(migliore_addio$correlazione, 3), "\n")
## 
## Most correlated book to The Long Goodbye: 6-Dance Dance Dance — Correlation: 0.723
# Plot del migliore
freq_plot_addio <- bind_rows(
  libri_tidy %>% filter(libro == migliore_addio$libro) %>% mutate(author = "Murakami"),
  altri_tidy %>% filter(libro == "The Long Goodbye") %>% mutate(author = "Addio")
) %>%
  mutate(word = str_extract(word, "[a-z']+")) %>%
  count(author, word) %>%
  group_by(author) %>%
  mutate(proportion = n / sum(n)) %>%
  select(-n) %>%
  pivot_wider(names_from = author, values_from = proportion)

ggplot(freq_plot_addio, aes(x = Addio, y = Murakami)) +
  geom_abline(color = "gray40", lty = 2) +
  geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
  scale_x_log10(labels = percent_format()) +
  scale_y_log10(labels = percent_format()) +
  labs(title = paste("The Long Goodbye vs", migliore_addio$libro),
       x = "The Long Goodbye — Chandler",
       y = paste(migliore_addio$libro, "— Murakami")) +
  theme_bw()
## Warning: Removed 8871 rows containing missing values or values outside the scale range
## (`geom_text()`).

# 6. PRIMO VS ULTIMO LIBRO DI MURAKAMI
# Identifica primo e ultimo per anno


cat("\nFirst book:", "1-Hear the Wind Sing")
## 
## First book: 1-Hear the Wind Sing
cat("\nLast book:", "15-The City and Its Uncertain", "\n")
## 
## Last book: 15-The City and Its Uncertain
# Frequenze primo vs ultimo
freq_primo_ultimo <- bind_rows(
  libri_tidy %>% filter(libro == "1-Hear the Wind Sing") %>% mutate(author = "First"),
  libri_tidy %>% filter(libro == "15-The City and Its Uncertain") %>% mutate(author = "Last")
) %>%
  mutate(word = str_extract(word, "[a-z']+")) %>%
  count(author, word) %>%
  group_by(author) %>%
  mutate(proportion = n / sum(n)) %>%
  select(-n) %>%
  pivot_wider(names_from = author, values_from = proportion) %>%
  drop_na() 

# Plot
ggplot(freq_primo_ultimo, aes(x = First, y = Last)) +
  geom_abline(color = "gray40", lty = 2) +
  geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
  scale_x_log10(labels = percent_format()) +
  scale_y_log10(labels = percent_format()) +
  labs(title = "First vs Last book",
       x = paste("First —", "1-Hear the Wind Sing"),
       y = paste("Last —", "15-The City and Its Uncertain")) +
  theme_bw()

# Correlazione
cor.test(freq_primo_ultimo$First, freq_primo_ultimo$Last)
## 
##  Pearson's product-moment correlation
## 
## data:  freq_primo_ultimo$First and freq_primo_ultimo$Last
## t = 27.442, df = 1941, p-value < 2.2e-16
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  0.4958980 0.5600109
## sample estimates:
##       cor 
## 0.5287081
# TF-IDF distintivo 
tf_idf_confronto <- libri_tidy %>%
  filter(libro %in% c("1-Hear the Wind Sing", "15-The City and Its Uncertain")) %>%
  count(libro, word) %>%
  bind_tf_idf(word, libro, n) %>%
  arrange(desc(tf_idf)) %>%
  group_by(libro) %>%
  top_n(15, tf_idf) %>%
  ungroup()

tf_idf_confronto %>%
  mutate(word = reorder_within(word, tf_idf, libro)) %>%
  ggplot(aes(word, tf_idf, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, ncol = 2, scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Distinctive words: first vs last book",
       x = NULL,
       y = "tf-idf")

# Trova parole presenti in ENTRAMBI i libri
parole_comuni_primo_ultimo <- freq_primo_ultimo %>%
  filter(!is.na(First), !is.na(Last)) %>%  
  mutate(media_prop = (First + Last) / 2) %>%  
  slice_max(media_prop, n = 20) %>%
  arrange(desc(media_prop))

parole_comuni_primo_ultimo
## # A tibble: 20 × 4
##    word       First      Last media_prop
##    <chr>      <dbl>     <dbl>      <dbl>
##  1 time    0.0130   0.0121       0.0125 
##  2 town    0.00288  0.0126       0.00772
##  3 people  0.00588  0.00454      0.00521
##  4 library 0.000240 0.00818      0.00421
##  5 rat     0.00840  0.0000206    0.00421
##  6 head    0.00360  0.00413      0.00387
##  7 day     0.00276  0.00465      0.00370
##  8 yeah    0.00720  0.000123     0.00366
##  9 looked  0.00384  0.00341      0.00363
## 10 boy     0.000120 0.00701      0.00356
## 11 beer    0.00684  0.0000822    0.00346
## 12 night   0.00360  0.00282      0.00321
## 13 girl    0.00504  0.00127      0.00316
## 14 eyes    0.00216  0.00413      0.00315
## 15 left    0.00264  0.00329      0.00296
## 16 nodded  0.00396  0.00173      0.00284
## 17 wall    0.000360 0.00520      0.00278
## 18 hand    0.00396  0.00150      0.00273
## 19 world   0.000480 0.00475      0.00261
## 20 words   0.00216  0.00296      0.00256
# Visualizzazione parole comuni
parole_comuni_primo_ultimo %>%
  mutate(word = reorder(word, media_prop)) %>%
  ggplot(aes(word, media_prop)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(title = paste("Most common words —", "1-Hear the Wind Sing", "and", "15-The City and Its Uncertain"),
       x = NULL,
       y = "Average relative frequency") +
  theme_bw()

# 7. TEMI RICORRENTI (PAROLE PRESENTI IN MOLTI LIBRI)
# Parole presenti in almeno 10 libri
temi_ricorrenti <- libri_tidy %>%
  distinct(libro, word) %>%
  count(word) %>%
  filter(n >= 10) %>%
  arrange(desc(n))

temi_ricorrenti
## # A tibble: 3,493 × 2
##    word          n
##    <chr>     <int>
##  1 1            15
##  2 abandoned    15
##  3 address      15
##  4 afraid       15
##  5 afternoon    15
##  6 age          15
##  7 aged         15
##  8 ago          15
##  9 ahead        15
## 10 air          15
## # ℹ 3,483 more rows
# Top temi per libro
libri_tidy %>%
  filter(libro %in% levels(libro)[1:8]) %>%
  semi_join(temi_ricorrenti, by = "word") %>%
  count(libro, word) %>%
  group_by(libro) %>%
  mutate(proportion = n / sum(n)) %>%
  slice_max(proportion, n = 10) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, proportion, libro)) %>%
  ggplot(aes(word, proportion, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, ncol = 3, scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Recurring themes per book (words in 10+ books)",
       x = NULL,
       y = "Relative frequency")

libri_tidy %>%
  filter(libro %in% levels(libro)[9:15]) %>%
  semi_join(temi_ricorrenti, by = "word") %>%
  count(libro, word) %>%
  group_by(libro) %>%
  mutate(proportion = n / sum(n)) %>%
  slice_max(proportion, n = 10) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, proportion, libro)) %>%
  ggplot(aes(word, proportion, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, ncol = 3, scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Recurring themes per book (words in 10+ books)",
       x = NULL,
       y = "Relative frequency")

# 8. EVOLUZIONE TEMPORALE 

libri_ordinati <- dfM %>%
  distinct(libro, anno) %>%
  arrange(anno)

libri_seq <- as.character(libri_ordinati$libro)

correlazioni_seq <- tibble(
  libro1 = character(),
  libro2 = character(),
  correlazione = numeric()
)

for (i in 1:(length(libri_seq) - 1)) {
  
  freq_temp <- bind_rows(
    libri_tidy %>% filter(libro == libri_seq[i]) %>% mutate(author = "Book1"),
    libri_tidy %>% filter(libro == libri_seq[i+1]) %>% mutate(author = "Book2")
  ) %>%
    mutate(word = str_extract(word, "[a-z']+")) %>%
    count(author, word) %>%
    group_by(author) %>%
    mutate(proportion = n / sum(n)) %>%
    select(-n) %>%
    pivot_wider(names_from = author, values_from = proportion) %>%
    filter(!is.na(Book1), !is.na(Book2))
  
  cor_val <- cor(freq_temp$Book1, freq_temp$Book2, use = "complete.obs")
  
  cat(i, "|", libri_seq[i], "→", libri_seq[i+1], 
      "| cor:", round(cor_val, 3), "\n")
  
  correlazioni_seq <- correlazioni_seq %>%
    add_row(libro1 = libri_seq[i], 
            libro2 = libri_seq[i+1], 
            correlazione = cor_val)
}
## 1 | 1-Hear the Wind Sing → 2-Pinball, 1973 | cor: 0.714 
## 2 | 2-Pinball, 1973 → 3-A Wild Sheep Chase | cor: 0.54 
## 3 | 3-A Wild Sheep Chase → 4-Hard Boiled Wonderland and the End of the World | cor: 0.533 
## 4 | 4-Hard Boiled Wonderland and the End of the World → 5-Norwegian wood | cor: 0.639 
## 5 | 5-Norwegian wood → 6-Dance Dance Dance | cor: 0.709 
## 6 | 6-Dance Dance Dance → 7-South of the Border West of the Sun | cor: 0.742 
## 7 | 7-South of the Border West of the Sun → 8-The Wind-Up Bird Chronicle | cor: 0.751 
## 8 | 8-The Wind-Up Bird Chronicle → 9-Sputnik Sweetheart | cor: 0.548 
## 9 | 9-Sputnik Sweetheart → 10-Kafka On The Shore | cor: 0.813 
## 10 | 10-Kafka On The Shore → 11-After Dark | cor: 0.71 
## 11 | 11-After Dark → 12-1Q84 | cor: 0.699 
## 12 | 12-1Q84 → 13-Colorless Tsukuru Tazaki | cor: 0.851 
## 13 | 13-Colorless Tsukuru Tazaki → 14-Killing Commendatore | cor: 0.794 
## 14 | 14-Killing Commendatore → 15-The City and Its Uncertain | cor: 0.623
# Plot
correlazioni_seq %>%
  mutate(ordine = row_number(),
         coppia = paste(str_trunc(libro1, 20), "→", str_trunc(libro2, 20))) %>%
  ggplot(aes(x = reorder(coppia, ordine), y = correlazione)) +
  geom_col(fill = "steelblue") +
  geom_hline(yintercept = mean(correlazioni_seq$correlazione), 
             lty = 2, color = "red") +
  coord_flip() +
  labs(title = "Correlation between consecutive books (by year)",
       x = NULL,
       y = "Correlation") +
  theme_minimal()

# 9. LIBRI POPOLARI 
libri_popolari <- c("5-Norwegian wood", 
                    "12-1Q84", 
                    "10-Kafka On The Shore")

# TF-IDF libri popolari
libri_tidy %>%
  filter(libro %in% libri_popolari) %>%
  count(libro, word) %>%
  bind_tf_idf(word, libro, n) %>%
  group_by(libro) %>%
  top_n(15, tf_idf) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, tf_idf, libro)) %>%
  ggplot(aes(word, tf_idf, fill = libro)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~libro, ncol = 2, scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Distinctive words — Popular books",
       x = NULL,
       y = "tf-idf")

# Parole comuni tra libri popolari
libri_tidy %>%
  filter(libro %in% libri_popolari) %>%
  count(libro, word) %>%
  group_by(word) %>%
  filter(n() == length(libri_popolari)) %>% 
  ungroup() %>%
  group_by(libro) %>%
  mutate(proportion = n / sum(n)) %>%
  ungroup() %>%
  group_by(word) %>%
  summarise(media_prop = mean(proportion)) %>%
  slice_max(media_prop, n = 20) %>%
  mutate(word = reorder(word, media_prop)) %>%
  ggplot(aes(word, media_prop)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(title = "Common words among popular books",
       x = NULL,
       y = "Average relative frequency") +
  theme_bw()

# Correlazioni tra libri popolari
for (i in 1:(length(libri_popolari) - 1)) {
  for (j in (i + 1):length(libri_popolari)) {
    
    freq_temp <- bind_rows(
      libri_tidy %>% filter(libro == libri_popolari[i]) %>% mutate(author = "Book1"),
      libri_tidy %>% filter(libro == libri_popolari[j]) %>% mutate(author = "Book2")
    ) %>%
      mutate(word = str_extract(word, "[a-z']+")) %>%
      count(author, word) %>%
      group_by(author) %>%
      mutate(proportion = n / sum(n)) %>%
      select(-n) %>%
      pivot_wider(names_from = author, values_from = proportion)
    
    cor_val <- cor(freq_temp$Book1, freq_temp$Book2, use = "complete.obs")
    
    cat("\nCorrelation", libri_popolari[i], "-", libri_popolari[j], 
        ":", round(cor_val, 3))
  }
}
## 
## Correlation 5-Norwegian wood - 12-1Q84 : 0.74
## Correlation 5-Norwegian wood - 10-Kafka On The Shore : 0.715
## Correlation 12-1Q84 - 10-Kafka On The Shore : 0.723

In questa sezione andiamo ad analizzare le correlazioni che sussistono tra i vari libri (i 15 libri di Murakami e i 3 libri di autori di cui Murakami è stato fortemente influenzato)

L’analisi si basa sul coefficiente di correlazione di Pearson applicato alle frequenze relative delle parole. Questo metodo consente di misurare quanto due testi condividano lo stesso vocabolario concentrandosi così sulla struttura linguistica dei testi.

Gli autori e i libri che ho selezionato per il confronto sono:

La correlazione più alta emerge con The Long Goodbye di Raymond Chandler (0.705), Questo dato conferma che Murakami è uno scrittore hard-boiled (autore di narrativa poliziesca che descrive il crimine in modo crudo e realistico, distaccandosi dall’enigma deduttivo classico). L’influenza chandleriana si manifesta attraverso un narratore in prima persona solitario e disincantato, ambientazioni urbane notturne, dialoghi asciutti e una violenza descritta senza moralismo.

Le parole condivise più frequenti (“world”, “eyes”, “looked”, “hand”, “time”) rimandano a una percezione fenomenologica della realtà, mentre termini come “bar”, “drink”, “guy”, “gun”, “cop” collocano l’esperienza narrativa in uno spazio noir riconoscibile. Murakami utilizza lo stile noir hard-boiled per raccontare la disillusione e l’alienazione delle persone attraverso il surrealismo e la malinconia.

La seconda affinità significativa emerge con George Orwell (1984, 0.586) anche se i valori non sono particolarmente alti. Di base lo stile di Murakami è meno linguistico e più tematico. Murakami condivide con Orwell l’idea di un potere pervasivo e impersonale (Big Brother trova un’eco nei Little People di 1Q84) e una profonda sfiducia nella possibilità di accedere a una realtà oggettiva e stabile. Tuttavia, mentre Orwell utilizza un vocabolario politico esplicito (“party”, “ministry”, “comrade”), Murakami preferisce un lessico psicologico ed emotivo (“feel”, “moment”, “love”). Orwell infatti descrive il funzionamento del potere mentre Murakami descrive l’effetto del potere sull’individuo.

Con F. Scott Fitzgerald (The Great Gatsby, 0.540) la somiglianza è moderata e passa soprattutto attraverso temi condivisi come la nostalgia per il passato, l’amore irraggiungibile e l’illusione di una felicità sempre rimandata. I due autori infatti sono particolarmente diversi sul piano stilistico. Fitzgerald costruisce un mondo barocco e glamour, dominato da nomi propri e simboli di ricchezza mentre Murakami riduce il linguaggio all’ordinario, privilegiando parole neutre e universali come “time”, “people” ed “eyes”.

Nel caso di Richard Brautigan, la correlazione è decisamente bassa (0.371), nonostante Murakami lo abbia tradotto e citato spesso come influenza. Probabilmente Murakami ha riutilizzato l’attitudine di Brautigan (ironia, frammentazione, leggerezza apparente) ma non il suo lessico. La bassa correlazione non indica quindi assenza di influenza, ma una influenza di tipo concettuale e meno linguistico.

Il confronto tra 1Q84 e 1984 (correlazione 0.575) conferma la natura di 1Q84 come omaggio tematico piuttosto che riscrittura. Il titolo del libro segnala esplicitamente la correlazione con Orwell (in giapponese, “Q” si pronuncia “kyū” = “9”), ma il suo vocabolario politico viene sostituito da uno emotivo e relazionale. Non è il sistema a essere descritto, ma la solitudine di chi lo abita.

Un discorso simile vale per Hear the Wind Sing e Trout Fishing in America (0.505). Entrambi sono esordi frammentari, costruiti su capitoli brevissimi e una voce narrante disillusa. Tuttavia, Murakami urbanizza l’estetica sostituendo la natura e l’ambiente con bar, città e riferimenti letterari. La tecnica è appresa, ma il contesto è già autonomo.

Successivamente sono andata a individuare il libro che più si avvicinasse al The Great Gatsby ed è risultato The Wind-Up Bird Chronicle (0.67). Il libro rivela una sorprendente affinità strutturale dal momento che in entrambi i casi abbiamo un narratore passivo che osserva il collasso di un sogno legato a una donna perduta, mentre il passato ritorna sotto forma di trauma. Tuttavia, Murakami sostituisce i nomi propri con simboli naturali, spostando il centro emotivo dall’individuo al mito.

Secondo l’analisi, la correlazione più alta è quella di The Long Goodbye e Dance Dance Dance (0.723). Dance Dance Dance è il romanzo in cui Murakami si avvicina di più al modello hard-boiled con un protagonista senza nome, investigazione informale, hotel come spazio liminale, violenza e corruzione. Qui Murakami non si limita a essere influenzato da Chandler, lo riscrive.

Il confronto tra il primo e l’ultimo romanzo (Hear the Wind Sing, 1979, e The City and Its Uncertain Walls, 2023) restituisce una correlazione moderata (0.529) che, considerato i 44 anni di distanza tra i due libri, può essere considerato un valore abbastanza alto. Le parole chiave condivise — “time”, “town”, “people” — indicano che il nucleo tematico ed esistenziale di Murakami è rimasto di per se invariato, mentre il lessico si sviluppa da uno colloquiale e americanizzato a uno simbolico e mitologico.

L’analisi delle correlazioni tra libri consecutivi mostra invece che Murakami nel tempo diventa sempre più simile a se stesso. Le correlazioni aumentano man mano che i libri vengono pubblicati e raggiungendo valori alti nelle opere più recenti. Questo suggerisce una progressiva cristallizzazione dello stile dello scrittore che si focalizza sempre meno sullo sperimentare e sempre di più sulla perfezione formale.

Con la correlazione possiamo andare a individuare 4 periodi: “Period 1 (1979-1985)” = “1-Hear the Wind Sing”, “2-Pinball, 1973”, “3-A Wild Sheep Chase”, che rappresenta la prima trillogia di murakami

“Period 2 (1985-1995)” = c(“4-Hard Boiled Wonderland and the End of the World”, “5-Norwegian wood”,“6-Dance Dance Dance”,“7-South of the Border West of the Sun”),

“Period 3 (1995-2005)” = c(“8-The Wind-Up Bird Chronicle”, “9-Sputnik Sweetheart”,“10-Kafka On The Shore”),

“Period 4 (2005-2023)” = c(“11-After Dark”, “12-1Q84”,“13-Colorless Tsukuru Tazaki”,“14-Killing Commendatore”,“15-The City and Its Uncertain”)

Se vogliamo cercare di individuare un patter strutturale tra i libri notiamo che circa 3.500 parole compaiono in almeno 10 dei 15 romanzi analizzati e costistuiscono un vero e proprio vocabolario murakamiano standard. Parole legate al tempo, alla percezione, al corpo e all’esistenza dominano costantemente, indipendentemente dalla trama ( “time”, “people”, “eyes”, “looked” “head”, “hand”, “day”, “night” “left”, “world”). Questo conferma l’esistenza di una palette lessicale stabile, su cui Murakami costruisce variazioni minime. La sua evoluzione non è stata rivoluzionaria ma incrementale: ogni libro aggiunge sfumature a una formula che si perfeziona senza mai rompersi.

Per concludere, sono andata ad analizzare i 3 romanzi più famosi dell’autore cercando di individuare i pattern ricorrenti che più vengono amati dal pubblico murakamiano. I libri sono Norwegian Wood, Kafka on the Shore e 1Q84. L’analisi delle correlazioni lessicali tra questi testi mostra valori elevati: Norwegian Wood–1Q84 (0.740), Norwegian Wood–Kafka on the Shore (0.715) e Kafka on the Shore–1Q84 (0.723), con una media complessiva di 0.726. Si tratta di una correlazione significativamente superiore alla media dell’intero corpus, indicando che i romanzi più amati dal pubblico sono anche quelli più simili tra loro dal punto di vista linguistico.

Dal punto di vista lessicale, questi testi condividono un nucleo comune estremamente stabile: parole come time, people, eyes, world, life, hand, head e looked compaiono con alta frequenza in tutti e tre. Questo vocabolario non rimanda a eventi specifici o a trame particolari, ma a una dimensione esistenziale astratta, fatta di percezione, corporeità minima e riflessione sul tempo. È proprio questa neutralità semantica, oscillante tra introspezione e malinconia, a costituire il cuore riconoscibile dell’esperienza murakamiana.

Le differenze tra i tre romanzi emergono invece nella distribuzione dei nomi propri. Norwegian Wood è dominato da nomi femminili (Naoko, Midori, Reiko), confermando la sua natura fortemente sentimentale e relazionale. Kafka on the Shore presenta un cast più ampio e corale, riflettendo una struttura narrativa complessa e frammentata. 1Q84, infine, mostra una perfetta parità tra protagonisti maschili e femminili (Tengo e Aomame), coerente con la sua costruzione duale. Tuttavia, al di sotto di queste differenze superficiali, il vocabolario di base rimane sorprendentemente uniforme.

Questi dati suggeriscono l’esistenza di una vera e propria formula linguistica del successo murakamiano. I romanzi che hanno avuto maggiore risonanza sono quelli che utilizzano in modo più puro e riconoscibile il lessico esistenziale standard dell’autore, senza deviazioni stilistiche marcate.

Questo risultato rafforza una delle conclusioni centrali dell’analisi comparativa nel quale si evidenzia come Murakami è diventato progressivamente un autore che scrive “per somiglianza”. La coerenza lessicale costituisce la chiave della sua riconoscibilità globale.

Il risultato è un corpus di 15 romanzi che sono variazioni sul tema dell’individuo smarrito nel tempo, che cerca connessioni fragili in un mondo opaco e alienante.